summaryrefslogtreecommitdiffstats
path: root/sound/soc/tegra
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/tegra')
-rw-r--r--sound/soc/tegra/Kconfig276
-rw-r--r--sound/soc/tegra/Makefile51
-rw-r--r--sound/soc/tegra/tegra186_asrc.c1046
-rw-r--r--sound/soc/tegra/tegra186_asrc.h112
-rw-r--r--sound/soc/tegra/tegra186_dspk.c555
-rw-r--r--sound/soc/tegra/tegra186_dspk.h70
-rw-r--r--sound/soc/tegra/tegra20_ac97.c465
-rw-r--r--sound/soc/tegra/tegra20_ac97.h86
-rw-r--r--sound/soc/tegra/tegra20_das.c205
-rw-r--r--sound/soc/tegra/tegra20_i2s.c514
-rw-r--r--sound/soc/tegra/tegra20_i2s.h150
-rw-r--r--sound/soc/tegra/tegra20_spdif.c432
-rw-r--r--sound/soc/tegra/tegra20_spdif.h457
-rw-r--r--sound/soc/tegra/tegra210_admaif.c872
-rw-r--r--sound/soc/tegra/tegra210_admaif.h162
-rw-r--r--sound/soc/tegra/tegra210_adx.c544
-rw-r--r--sound/soc/tegra/tegra210_adx.h72
-rw-r--r--sound/soc/tegra/tegra210_ahub.c1441
-rw-r--r--sound/soc/tegra/tegra210_ahub.h129
-rw-r--r--sound/soc/tegra/tegra210_amx.c607
-rw-r--r--sound/soc/tegra/tegra210_amx.h93
-rw-r--r--sound/soc/tegra/tegra210_dmic.c570
-rw-r--r--sound/soc/tegra/tegra210_dmic.h82
-rw-r--r--sound/soc/tegra/tegra210_i2s.c967
-rw-r--r--sound/soc/tegra/tegra210_i2s.h126
-rw-r--r--sound/soc/tegra/tegra210_mbdrc.c1014
-rw-r--r--sound/soc/tegra/tegra210_mbdrc.h215
-rw-r--r--sound/soc/tegra/tegra210_mixer.c686
-rw-r--r--sound/soc/tegra/tegra210_mixer.h100
-rw-r--r--sound/soc/tegra/tegra210_mvc.c778
-rw-r--r--sound/soc/tegra/tegra210_mvc.h122
-rw-r--r--sound/soc/tegra/tegra210_ope.c419
-rw-r--r--sound/soc/tegra/tegra210_ope.h90
-rw-r--r--sound/soc/tegra/tegra210_peq.c434
-rw-r--r--sound/soc/tegra/tegra210_peq.h56
-rw-r--r--sound/soc/tegra/tegra210_sfc.c3643
-rw-r--r--sound/soc/tegra/tegra210_sfc.h78
-rw-r--r--sound/soc/tegra/tegra30_ahub.c690
-rw-r--r--sound/soc/tegra/tegra30_ahub.h525
-rw-r--r--sound/soc/tegra/tegra30_i2s.c574
-rw-r--r--sound/soc/tegra/tegra30_i2s.h240
-rw-r--r--sound/soc/tegra/tegra_asoc_machine.c903
-rw-r--r--sound/soc/tegra/tegra_asoc_machine.h51
-rw-r--r--sound/soc/tegra/tegra_audio_graph_card.c257
-rw-r--r--sound/soc/tegra/tegra_cif.h65
-rw-r--r--sound/soc/tegra/tegra_pcm.c222
-rw-r--r--sound/soc/tegra/tegra_pcm.h41
-rw-r--r--sound/soc/tegra/tegra_wm8903.c188
48 files changed, 21475 insertions, 0 deletions
diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig
new file mode 100644
index 000000000..b6712a3d1
--- /dev/null
+++ b/sound/soc/tegra/Kconfig
@@ -0,0 +1,276 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_SOC_TEGRA
+ tristate "SoC Audio for the Tegra System-on-Chip"
+ depends on (ARCH_TEGRA && TEGRA20_APB_DMA) || COMPILE_TEST
+ depends on COMMON_CLK
+ depends on RESET_CONTROLLER
+ select REGMAP_MMIO
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ help
+ Say Y or M here if you want support for SoC audio on Tegra.
+
+if SND_SOC_TEGRA
+
+config SND_SOC_TEGRA20_AC97
+ tristate "Tegra20 AC97 interface"
+ select SND_SOC_AC97_BUS
+ select SND_SOC_TEGRA20_DAS
+ help
+ Say Y or M if you want to add support for codecs attached to the
+ Tegra20 AC97 interface. You will also need to select the individual
+ machine drivers to support below.
+
+config SND_SOC_TEGRA20_DAS
+ tristate "Tegra20 DAS module"
+ help
+ Say Y or M if you want to add support for the Tegra20 DAS module.
+ You will also need to select the individual machine drivers to
+ support below.
+
+config SND_SOC_TEGRA20_I2S
+ tristate "Tegra20 I2S interface"
+ select SND_SOC_TEGRA20_DAS
+ help
+ Say Y or M if you want to add support for codecs attached to the
+ Tegra20 I2S interface. You will also need to select the individual
+ machine drivers to support below.
+
+config SND_SOC_TEGRA20_SPDIF
+ tristate "Tegra20 SPDIF interface"
+ help
+ Say Y or M if you want to add support for the Tegra20 SPDIF interface.
+ You will also need to select the individual machine drivers to support
+ below.
+
+config SND_SOC_TEGRA30_AHUB
+ tristate "Tegra30 AHUB module"
+ help
+ Say Y or M if you want to add support for the Tegra30 AHUB module.
+ You will also need to select the individual machine drivers to
+ support below.
+
+config SND_SOC_TEGRA30_I2S
+ tristate "Tegra30 I2S interface"
+ select SND_SOC_TEGRA30_AHUB
+ help
+ Say Y or M if you want to add support for codecs attached to the
+ Tegra30 I2S interface. You will also need to select the individual
+ machine drivers to support below.
+
+config SND_SOC_TEGRA210_AHUB
+ tristate "Tegra210 AHUB module"
+ help
+ Config to enable Audio Hub (AHUB) module, which comprises of a
+ switch called Audio Crossbar (AXBAR) used to configure or modify
+ the audio routing path between various HW accelerators present in
+ AHUB.
+ Say Y or M if you want to add support for Tegra210 AHUB module.
+
+config SND_SOC_TEGRA210_DMIC
+ tristate "Tegra210 DMIC module"
+ help
+ Config to enable the Digital MIC (DMIC) controller which is used
+ to interface with Pulse Density Modulation (PDM) input devices.
+ The DMIC controller implements a converter to convert PDM signals
+ to Pulse Code Modulation (PCM) signals. This can be viewed as a
+ PDM receiver.
+ Say Y or M if you want to add support for Tegra210 DMIC module.
+
+config SND_SOC_TEGRA210_I2S
+ tristate "Tegra210 I2S module"
+ help
+ Config to enable the Inter-IC Sound (I2S) Controller which
+ implements full-duplex and bidirectional and single direction
+ point-to-point serial interfaces. It can interface with 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
+ Config to enable the Digital Speaker Controller (DSPK) which
+ converts the multi-bit Pulse Code Modulation (PCM) audio input to
+ oversampled 1-bit Pulse Density Modulation (PDM) output. From the
+ signal flow perspective DSPK can be viewed as a PDM transmitter
+ that up-samples the input to the desired sampling rate by
+ interpolation and then converts the oversampled PCM input to
+ the desired 1-bit output via Delta Sigma Modulation (DSM).
+ Say Y or M if you want to add support for Tegra186 DSPK module.
+
+config SND_SOC_TEGRA210_ADMAIF
+ tristate "Tegra210 ADMAIF module"
+ help
+ Config to enable ADMAIF which is the interface between ADMA and
+ Audio Hub (AHUB). Each ADMA channel that sends/receives data to/
+ from AHUB must interface through an ADMAIF channel. ADMA channel
+ sending data to AHUB pairs with an ADMAIF Tx channel, where as
+ ADMA channel receiving data from AHUB pairs with an ADMAIF Rx
+ channel. Buffer size is configurable for each ADMAIIF channel.
+ Say Y or M if you want to add support for Tegra210 ADMAIF module.
+
+config SND_SOC_TEGRA210_MVC
+ tristate "Tegra210 MVC module"
+ help
+ Config to enable the digital Master Volume Controller (MVC) which
+ provides gain or attenuation to a digital signal path. It can be
+ used in input or output signal path. It can be used either for
+ per-stream volume control or for master volume control.
+ Say Y or M if you want to add support for Tegra210 MVC module.
+
+config SND_SOC_TEGRA210_SFC
+ tristate "Tegra210 SFC module"
+ help
+ Config to enable the Sampling Frequency Converter (SFC) which
+ converts the sampling frequency of input signal to another
+ frequency. It supports sampling frequency conversion of streams
+ upto 2 channels (stereo).
+ Say Y or M if you want to add support for Tegra210 SFC module.
+
+config SND_SOC_TEGRA210_AMX
+ tristate "Tegra210 AMX module"
+ help
+ Config to enable the Audio Multiplexer (AMX) which can multiplex
+ four input streams (each of up to 16 channels) and generate
+ output stream (of up to 16 channels). A byte RAM helps to form an
+ output frame by any combination of bytes from the input frames.
+ Say Y or M if you want to add support for Tegra210 AMX module.
+
+config SND_SOC_TEGRA210_ADX
+ tristate "Tegra210 ADX module"
+ help
+ Config to enable the Audio Demultiplexer (ADX) which takes an
+ input stream (up to 16 channels) and demultiplexes it into four
+ output streams (each of up to 16 channels). A byte RAM helps to
+ form output frames by any combination of bytes from the input
+ frame. Its design is identical to that of byte RAM in the AMX
+ except that the data flow direction is reversed.
+ Say Y or M if you want to add support for Tegra210 ADX module.
+
+config SND_SOC_TEGRA210_MIXER
+ tristate "Tegra210 Mixer module"
+ help
+ Config to enable the Mixer module which can help to mix multiple
+ audio streams. It supports mixing of upto 10 input streams,
+ where each stream can contain maximum of 8 channels. It supports
+ 5 output each of which can be a mix of any combination of 10
+ input streams.
+ Say Y or M if you want to add support for Tegra210 Mixer module.
+
+config SND_SOC_TEGRA_AUDIO_GRAPH_CARD
+ tristate "Audio Graph Card based Tegra driver"
+ depends on SND_AUDIO_GRAPH_CARD
+ help
+ Config to enable Tegra audio machine driver based on generic
+ audio graph driver. It is a thin driver written to customize
+ few things for Tegra audio. Most of the code is re-used from
+ audio graph driver and the same DT bindings are used.
+
+config SND_SOC_TEGRA_MACHINE_DRV
+ tristate
+
+config SND_SOC_TEGRA_RT5640
+ tristate "SoC Audio support for Tegra boards using an RT5640 codec"
+ depends on I2C && GPIOLIB
+ select SND_SOC_TEGRA_MACHINE_DRV
+ select SND_SOC_RT5640
+ help
+ Say Y or M here if you want to add support for SoC audio on Tegra
+ boards using the RT5640 codec, such as Dalmore.
+
+config SND_SOC_TEGRA_WM8753
+ tristate "SoC Audio support for Tegra boards using a WM8753 codec"
+ depends on I2C && GPIOLIB
+ select SND_SOC_TEGRA_MACHINE_DRV
+ select SND_SOC_WM8753
+ help
+ Say Y or M here if you want to add support for SoC audio on Tegra
+ boards using the WM8753 codec, such as Whistler.
+
+config SND_SOC_TEGRA_WM8903
+ tristate "SoC Audio support for Tegra boards using a WM8903 codec"
+ depends on I2C && GPIOLIB
+ select SND_SOC_TEGRA_MACHINE_DRV
+ select SND_SOC_WM8903
+ help
+ Say Y or M here if you want to add support for SoC audio on Tegra
+ boards using the WM8093 codec. Currently, the supported boards are
+ Harmony, Ventana, Seaboard, Kaen, and Aebl.
+
+config SND_SOC_TEGRA_WM9712
+ tristate "SoC Audio support for Tegra boards using a WM9712 codec"
+ depends on GPIOLIB
+ select SND_SOC_TEGRA_MACHINE_DRV
+ select SND_SOC_TEGRA20_AC97
+ select SND_SOC_WM9712
+ help
+ Say Y or M here if you want to add support for SoC audio on Tegra
+ boards using the WM9712 (or compatible) codec.
+
+config SND_SOC_TEGRA_TRIMSLICE
+ tristate "SoC Audio support for TrimSlice board"
+ depends on I2C
+ select SND_SOC_TEGRA_MACHINE_DRV
+ select SND_SOC_TLV320AIC23_I2C
+ help
+ Say Y or M here if you want to add support for SoC audio on the
+ TrimSlice platform.
+
+config SND_SOC_TEGRA_ALC5632
+ tristate "SoC Audio support for Tegra boards using an ALC5632 codec"
+ depends on I2C && GPIOLIB
+ select SND_SOC_TEGRA_MACHINE_DRV
+ select SND_SOC_ALC5632
+ help
+ Say Y or M here if you want to add support for SoC audio on the
+ Toshiba AC100 netbook.
+
+config SND_SOC_TEGRA_MAX98090
+ tristate "SoC Audio support for Tegra boards using a MAX98090 codec"
+ depends on I2C && GPIOLIB
+ select SND_SOC_TEGRA_MACHINE_DRV
+ select SND_SOC_MAX98090
+ help
+ Say Y or M here if you want to add support for SoC audio on Tegra
+ boards using the MAX98090 codec, such as Venice2.
+
+config SND_SOC_TEGRA_RT5677
+ tristate "SoC Audio support for Tegra boards using a RT5677 codec"
+ depends on I2C && GPIOLIB
+ select SND_SOC_TEGRA_MACHINE_DRV
+ select SND_SOC_RT5677
+ help
+ Say Y or M here if you want to add support for SoC audio on Tegra
+ boards using the RT5677 codec, such as Ryu.
+
+config SND_SOC_TEGRA_SGTL5000
+ tristate "SoC Audio support for Tegra boards using a SGTL5000 codec"
+ depends on I2C && GPIOLIB
+ select SND_SOC_TEGRA_MACHINE_DRV
+ select SND_SOC_SGTL5000
+ help
+ Say Y or M here if you want to add support for SoC audio on Tegra
+ boards using the SGTL5000 codec, such as Apalis T30, Apalis TK1 or
+ Colibri T30.
+
+endif
diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile
new file mode 100644
index 000000000..b723c78e6
--- /dev/null
+++ b/sound/soc/tegra/Makefile
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: GPL-2.0
+# Tegra platform Support
+snd-soc-tegra-pcm-objs := tegra_pcm.o
+snd-soc-tegra-utils-objs += tegra_asoc_utils.o
+snd-soc-tegra20-ac97-objs := tegra20_ac97.o
+snd-soc-tegra20-das-objs := tegra20_das.o
+snd-soc-tegra20-i2s-objs := tegra20_i2s.o
+snd-soc-tegra20-spdif-objs := tegra20_spdif.o
+snd-soc-tegra30-ahub-objs := tegra30_ahub.o
+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
+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
+obj-$(CONFIG_SND_SOC_TEGRA20_DAS) += snd-soc-tegra20-das.o
+obj-$(CONFIG_SND_SOC_TEGRA20_I2S) += snd-soc-tegra20-i2s.o
+obj-$(CONFIG_SND_SOC_TEGRA20_SPDIF) += snd-soc-tegra20-spdif.o
+obj-$(CONFIG_SND_SOC_TEGRA30_AHUB) += snd-soc-tegra30-ahub.o
+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
+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
+snd-soc-tegra-machine-objs := tegra_asoc_machine.o
+snd-soc-tegra-audio-graph-card-objs := tegra_audio_graph_card.o
+
+obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o
+obj-$(CONFIG_SND_SOC_TEGRA_MACHINE_DRV) += snd-soc-tegra-machine.o
+obj-$(CONFIG_SND_SOC_TEGRA_AUDIO_GRAPH_CARD) += snd-soc-tegra-audio-graph-card.o
diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c
new file mode 100644
index 000000000..9f12faaa6
--- /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 000000000..094fcc723
--- /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/tegra186_dspk.c b/sound/soc/tegra/tegra186_dspk.c
new file mode 100644
index 000000000..a74c980ee
--- /dev/null
+++ b/sound/soc/tegra/tegra186_dspk.c
@@ -0,0 +1,555 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// tegra186_dspk.c - Tegra186 DSPK driver
+//
+// Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved.
+
+#include <linux/clk.h>
+#include <linux/device.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_params.h>
+#include <sound/soc.h>
+#include "tegra186_dspk.h"
+#include "tegra_cif.h"
+
+static const struct reg_default tegra186_dspk_reg_defaults[] = {
+ { TEGRA186_DSPK_RX_INT_MASK, 0x00000007 },
+ { TEGRA186_DSPK_RX_CIF_CTRL, 0x00007700 },
+ { TEGRA186_DSPK_CG, 0x00000001 },
+ { TEGRA186_DSPK_CORE_CTRL, 0x00000310 },
+ { TEGRA186_DSPK_CODEC_CTRL, 0x03000000 },
+};
+
+static int tegra186_dspk_get_fifo_th(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = dspk->rx_fifo_th;
+
+ return 0;
+}
+
+static int tegra186_dspk_put_fifo_th(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+ int value = ucontrol->value.integer.value[0];
+
+ if (value == dspk->rx_fifo_th)
+ return 0;
+
+ dspk->rx_fifo_th = value;
+
+ return 1;
+}
+
+static int tegra186_dspk_get_osr_val(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = dspk->osr_val;
+
+ return 0;
+}
+
+static int tegra186_dspk_put_osr_val(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == dspk->osr_val)
+ return 0;
+
+ dspk->osr_val = value;
+
+ return 1;
+}
+
+static int tegra186_dspk_get_pol_sel(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = dspk->lrsel;
+
+ return 0;
+}
+
+static int tegra186_dspk_put_pol_sel(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == dspk->lrsel)
+ return 0;
+
+ dspk->lrsel = value;
+
+ return 1;
+}
+
+static int tegra186_dspk_get_ch_sel(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = dspk->ch_sel;
+
+ return 0;
+}
+
+static int tegra186_dspk_put_ch_sel(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == dspk->ch_sel)
+ return 0;
+
+ dspk->ch_sel = value;
+
+ return 1;
+}
+
+static int tegra186_dspk_get_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = dspk->mono_to_stereo;
+
+ return 0;
+}
+
+static int tegra186_dspk_put_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == dspk->mono_to_stereo)
+ return 0;
+
+ dspk->mono_to_stereo = value;
+
+ return 1;
+}
+
+static int tegra186_dspk_get_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = dspk->stereo_to_mono;
+
+ return 0;
+}
+
+static int tegra186_dspk_put_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol);
+ struct tegra186_dspk *dspk = snd_soc_component_get_drvdata(codec);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == dspk->stereo_to_mono)
+ return 0;
+
+ dspk->stereo_to_mono = value;
+
+ return 1;
+}
+
+static int __maybe_unused tegra186_dspk_runtime_suspend(struct device *dev)
+{
+ struct tegra186_dspk *dspk = dev_get_drvdata(dev);
+
+ regcache_cache_only(dspk->regmap, true);
+ regcache_mark_dirty(dspk->regmap);
+
+ clk_disable_unprepare(dspk->clk_dspk);
+
+ return 0;
+}
+
+static int __maybe_unused tegra186_dspk_runtime_resume(struct device *dev)
+{
+ struct tegra186_dspk *dspk = dev_get_drvdata(dev);
+ int err;
+
+ err = clk_prepare_enable(dspk->clk_dspk);
+ if (err) {
+ dev_err(dev, "failed to enable DSPK clock, err: %d\n", err);
+ return err;
+ }
+
+ regcache_cache_only(dspk->regmap, false);
+ regcache_sync(dspk->regmap);
+
+ return 0;
+}
+
+static int tegra186_dspk_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct tegra186_dspk *dspk = snd_soc_dai_get_drvdata(dai);
+ unsigned int channels, srate, dspk_clk;
+ struct device *dev = dai->dev;
+ struct tegra_cif_conf cif_conf;
+ unsigned int max_th;
+ int err;
+
+ memset(&cif_conf, 0, sizeof(struct tegra_cif_conf));
+
+ channels = params_channels(params);
+ cif_conf.audio_ch = channels;
+
+ /* Client channel */
+ switch (dspk->ch_sel) {
+ case DSPK_CH_SELECT_LEFT:
+ case DSPK_CH_SELECT_RIGHT:
+ cif_conf.client_ch = 1;
+ break;
+ case DSPK_CH_SELECT_STEREO:
+ cif_conf.client_ch = 2;
+ break;
+ default:
+ dev_err(dev, "Invalid DSPK client channels\n");
+ return -EINVAL;
+ }
+
+ cif_conf.client_bits = TEGRA_ACIF_BITS_24;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ cif_conf.audio_bits = TEGRA_ACIF_BITS_16;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ cif_conf.audio_bits = TEGRA_ACIF_BITS_32;
+ break;
+ default:
+ dev_err(dev, "unsupported format!\n");
+ return -EOPNOTSUPP;
+ }
+
+ srate = params_rate(params);
+
+ /* RX FIFO threshold in terms of frames */
+ max_th = (TEGRA186_DSPK_RX_FIFO_DEPTH / cif_conf.audio_ch) - 1;
+
+ if (dspk->rx_fifo_th > max_th)
+ dspk->rx_fifo_th = max_th;
+
+ cif_conf.threshold = dspk->rx_fifo_th;
+ cif_conf.mono_conv = dspk->mono_to_stereo;
+ cif_conf.stereo_conv = dspk->stereo_to_mono;
+
+ tegra_set_cif(dspk->regmap, TEGRA186_DSPK_RX_CIF_CTRL,
+ &cif_conf);
+
+ /*
+ * DSPK clock and PDM codec clock should be synchronous with 4:1 ratio,
+ * this is because it takes 4 clock cycles to send out one sample to
+ * codec by sigma delta modulator. Finally the clock rate is a multiple
+ * of 'Over Sampling Ratio', 'Sample Rate' and 'Interface Clock Ratio'.
+ */
+ dspk_clk = (DSPK_OSR_FACTOR << dspk->osr_val) * srate * DSPK_CLK_RATIO;
+
+ err = clk_set_rate(dspk->clk_dspk, dspk_clk);
+ if (err) {
+ dev_err(dev, "can't set DSPK clock rate %u, err: %d\n",
+ dspk_clk, err);
+
+ return err;
+ }
+
+ regmap_update_bits(dspk->regmap,
+ /* Reg */
+ TEGRA186_DSPK_CORE_CTRL,
+ /* Mask */
+ TEGRA186_DSPK_OSR_MASK |
+ TEGRA186_DSPK_CHANNEL_SELECT_MASK |
+ TEGRA186_DSPK_CTRL_LRSEL_POLARITY_MASK,
+ /* Value */
+ (dspk->osr_val << DSPK_OSR_SHIFT) |
+ ((dspk->ch_sel + 1) << CH_SEL_SHIFT) |
+ (dspk->lrsel << LRSEL_POL_SHIFT));
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops tegra186_dspk_dai_ops = {
+ .hw_params = tegra186_dspk_hw_params,
+};
+
+static struct snd_soc_dai_driver tegra186_dspk_dais[] = {
+ {
+ .name = "DSPK-CIF",
+ .playback = {
+ .stream_name = "CIF-Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ },
+ {
+ .name = "DSPK-DAP",
+ .playback = {
+ .stream_name = "DAP-Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &tegra186_dspk_dai_ops,
+ .symmetric_rate = 1,
+ },
+};
+
+static const struct snd_soc_dapm_widget tegra186_dspk_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("RX", NULL, 0, TEGRA186_DSPK_ENABLE, 0, 0),
+ SND_SOC_DAPM_SPK("SPK", NULL),
+};
+
+static const struct snd_soc_dapm_route tegra186_dspk_routes[] = {
+ { "XBAR-Playback", NULL, "XBAR-TX" },
+ { "CIF-Playback", NULL, "XBAR-Playback" },
+ { "RX", NULL, "CIF-Playback" },
+ { "DAP-Playback", NULL, "RX" },
+ { "SPK", NULL, "DAP-Playback" },
+};
+
+static const char * const tegra186_dspk_ch_sel_text[] = {
+ "Left", "Right", "Stereo",
+};
+
+static const struct soc_enum tegra186_dspk_ch_sel_enum =
+ SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(tegra186_dspk_ch_sel_text),
+ tegra186_dspk_ch_sel_text);
+
+static const char * const tegra186_dspk_osr_text[] = {
+ "OSR_32", "OSR_64", "OSR_128", "OSR_256",
+};
+
+static const struct soc_enum tegra186_dspk_osr_enum =
+ SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(tegra186_dspk_osr_text),
+ tegra186_dspk_osr_text);
+
+static const char * const tegra186_dspk_lrsel_text[] = {
+ "Left", "Right",
+};
+
+static const char * const tegra186_dspk_mono_conv_text[] = {
+ "Zero", "Copy",
+};
+
+static const struct soc_enum tegra186_dspk_mono_conv_enum =
+ SOC_ENUM_SINGLE(SND_SOC_NOPM, 0,
+ ARRAY_SIZE(tegra186_dspk_mono_conv_text),
+ tegra186_dspk_mono_conv_text);
+
+static const char * const tegra186_dspk_stereo_conv_text[] = {
+ "CH0", "CH1", "AVG",
+};
+
+static const struct soc_enum tegra186_dspk_stereo_conv_enum =
+ SOC_ENUM_SINGLE(SND_SOC_NOPM, 0,
+ ARRAY_SIZE(tegra186_dspk_stereo_conv_text),
+ tegra186_dspk_stereo_conv_text);
+
+static const struct soc_enum tegra186_dspk_lrsel_enum =
+ SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(tegra186_dspk_lrsel_text),
+ tegra186_dspk_lrsel_text);
+
+static const struct snd_kcontrol_new tegrat186_dspk_controls[] = {
+ SOC_SINGLE_EXT("FIFO Threshold", SND_SOC_NOPM, 0,
+ TEGRA186_DSPK_RX_FIFO_DEPTH - 1, 0,
+ tegra186_dspk_get_fifo_th, tegra186_dspk_put_fifo_th),
+ SOC_ENUM_EXT("OSR Value", tegra186_dspk_osr_enum,
+ tegra186_dspk_get_osr_val, tegra186_dspk_put_osr_val),
+ SOC_ENUM_EXT("LR Polarity Select", tegra186_dspk_lrsel_enum,
+ tegra186_dspk_get_pol_sel, tegra186_dspk_put_pol_sel),
+ SOC_ENUM_EXT("Channel Select", tegra186_dspk_ch_sel_enum,
+ tegra186_dspk_get_ch_sel, tegra186_dspk_put_ch_sel),
+ SOC_ENUM_EXT("Mono To Stereo", tegra186_dspk_mono_conv_enum,
+ tegra186_dspk_get_mono_to_stereo,
+ tegra186_dspk_put_mono_to_stereo),
+ SOC_ENUM_EXT("Stereo To Mono", tegra186_dspk_stereo_conv_enum,
+ tegra186_dspk_get_stereo_to_mono,
+ tegra186_dspk_put_stereo_to_mono),
+};
+
+static const struct snd_soc_component_driver tegra186_dspk_cmpnt = {
+ .dapm_widgets = tegra186_dspk_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra186_dspk_widgets),
+ .dapm_routes = tegra186_dspk_routes,
+ .num_dapm_routes = ARRAY_SIZE(tegra186_dspk_routes),
+ .controls = tegrat186_dspk_controls,
+ .num_controls = ARRAY_SIZE(tegrat186_dspk_controls),
+};
+
+static bool tegra186_dspk_wr_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA186_DSPK_RX_INT_MASK ... TEGRA186_DSPK_RX_CIF_CTRL:
+ case TEGRA186_DSPK_ENABLE ... TEGRA186_DSPK_CG:
+ case TEGRA186_DSPK_CORE_CTRL ... TEGRA186_DSPK_CODEC_CTRL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra186_dspk_rd_reg(struct device *dev, unsigned int reg)
+{
+ if (tegra186_dspk_wr_reg(dev, reg))
+ return true;
+
+ switch (reg) {
+ case TEGRA186_DSPK_RX_STATUS:
+ case TEGRA186_DSPK_RX_INT_STATUS:
+ case TEGRA186_DSPK_STATUS:
+ case TEGRA186_DSPK_INT_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra186_dspk_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA186_DSPK_RX_STATUS:
+ case TEGRA186_DSPK_RX_INT_STATUS:
+ case TEGRA186_DSPK_STATUS:
+ case TEGRA186_DSPK_INT_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tegra186_dspk_regmap = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA186_DSPK_CODEC_CTRL,
+ .writeable_reg = tegra186_dspk_wr_reg,
+ .readable_reg = tegra186_dspk_rd_reg,
+ .volatile_reg = tegra186_dspk_volatile_reg,
+ .reg_defaults = tegra186_dspk_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tegra186_dspk_reg_defaults),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct of_device_id tegra186_dspk_of_match[] = {
+ { .compatible = "nvidia,tegra186-dspk" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra186_dspk_of_match);
+
+static int tegra186_dspk_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tegra186_dspk *dspk;
+ void __iomem *regs;
+ int err;
+
+ dspk = devm_kzalloc(dev, sizeof(*dspk), GFP_KERNEL);
+ if (!dspk)
+ return -ENOMEM;
+
+ dspk->osr_val = DSPK_OSR_64;
+ dspk->lrsel = DSPK_LRSEL_LEFT;
+ dspk->ch_sel = DSPK_CH_SELECT_STEREO;
+ dspk->mono_to_stereo = 0; /* "Zero" */
+
+ dev_set_drvdata(dev, dspk);
+
+ dspk->clk_dspk = devm_clk_get(dev, "dspk");
+ if (IS_ERR(dspk->clk_dspk)) {
+ dev_err(dev, "can't retrieve DSPK clock\n");
+ return PTR_ERR(dspk->clk_dspk);
+ }
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ dspk->regmap = devm_regmap_init_mmio(dev, regs, &tegra186_dspk_regmap);
+ if (IS_ERR(dspk->regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ return PTR_ERR(dspk->regmap);
+ }
+
+ regcache_cache_only(dspk->regmap, true);
+
+ err = devm_snd_soc_register_component(dev, &tegra186_dspk_cmpnt,
+ tegra186_dspk_dais,
+ ARRAY_SIZE(tegra186_dspk_dais));
+ if (err) {
+ dev_err(dev, "can't register DSPK component, err: %d\n",
+ err);
+ return err;
+ }
+
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static int tegra186_dspk_platform_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra186_dspk_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra186_dspk_runtime_suspend,
+ tegra186_dspk_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver tegra186_dspk_driver = {
+ .driver = {
+ .name = "tegra186-dspk",
+ .of_match_table = tegra186_dspk_of_match,
+ .pm = &tegra186_dspk_pm_ops,
+ },
+ .probe = tegra186_dspk_platform_probe,
+ .remove = tegra186_dspk_platform_remove,
+};
+module_platform_driver(tegra186_dspk_driver);
+
+MODULE_AUTHOR("Mohan Kumar <mkumard@nvidia.com>");
+MODULE_AUTHOR("Sameer Pujar <spujar@nvidia.com>");
+MODULE_DESCRIPTION("Tegra186 ASoC DSPK driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/tegra/tegra186_dspk.h b/sound/soc/tegra/tegra186_dspk.h
new file mode 100644
index 000000000..b2a879065
--- /dev/null
+++ b/sound/soc/tegra/tegra186_dspk.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra186_dspk.h - Definitions for Tegra186 DSPK driver
+ *
+ * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved.
+ *
+ */
+
+#ifndef __TEGRA186_DSPK_H__
+#define __TEGRA186_DSPK_H__
+
+/* Register offsets from DSPK BASE */
+#define TEGRA186_DSPK_RX_STATUS 0x0c
+#define TEGRA186_DSPK_RX_INT_STATUS 0x10
+#define TEGRA186_DSPK_RX_INT_MASK 0x14
+#define TEGRA186_DSPK_RX_INT_SET 0x18
+#define TEGRA186_DSPK_RX_INT_CLEAR 0x1c
+#define TEGRA186_DSPK_RX_CIF_CTRL 0x20
+#define TEGRA186_DSPK_ENABLE 0x40
+#define TEGRA186_DSPK_SOFT_RESET 0x44
+#define TEGRA186_DSPK_CG 0x48
+#define TEGRA186_DSPK_STATUS 0x4c
+#define TEGRA186_DSPK_INT_STATUS 0x50
+#define TEGRA186_DSPK_CORE_CTRL 0x60
+#define TEGRA186_DSPK_CODEC_CTRL 0x64
+
+/* DSPK CORE CONTROL fields */
+#define CH_SEL_SHIFT 8
+#define TEGRA186_DSPK_CHANNEL_SELECT_MASK (0x3 << CH_SEL_SHIFT)
+#define DSPK_OSR_SHIFT 4
+#define TEGRA186_DSPK_OSR_MASK (0x3 << DSPK_OSR_SHIFT)
+#define LRSEL_POL_SHIFT 0
+#define TEGRA186_DSPK_CTRL_LRSEL_POLARITY_MASK (0x1 << LRSEL_POL_SHIFT)
+#define TEGRA186_DSPK_RX_FIFO_DEPTH 64
+
+#define DSPK_OSR_FACTOR 32
+
+/* DSPK interface clock ratio */
+#define DSPK_CLK_RATIO 4
+
+enum tegra_dspk_osr {
+ DSPK_OSR_32,
+ DSPK_OSR_64,
+ DSPK_OSR_128,
+ DSPK_OSR_256,
+};
+
+enum tegra_dspk_ch_sel {
+ DSPK_CH_SELECT_LEFT,
+ DSPK_CH_SELECT_RIGHT,
+ DSPK_CH_SELECT_STEREO,
+};
+
+enum tegra_dspk_lrsel {
+ DSPK_LRSEL_LEFT,
+ DSPK_LRSEL_RIGHT,
+};
+
+struct tegra186_dspk {
+ unsigned int rx_fifo_th;
+ unsigned int osr_val;
+ unsigned int lrsel;
+ unsigned int ch_sel;
+ unsigned int mono_to_stereo;
+ unsigned int stereo_to_mono;
+ struct clk *clk_dspk;
+ struct regmap *regmap;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c
new file mode 100644
index 000000000..87facfbcd
--- /dev/null
+++ b/sound/soc/tegra/tegra20_ac97.c
@@ -0,0 +1,465 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tegra20_ac97.c - Tegra20 AC97 platform driver
+ *
+ * Copyright (c) 2012 Lucas Stach <dev@lynxeye.de>
+ *
+ * Partly based on code copyright/by:
+ *
+ * Copyright (c) 2011,2012 Toradex Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.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>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "tegra20_ac97.h"
+
+#define DRV_NAME "tegra20-ac97"
+
+static struct tegra20_ac97 *workdata;
+
+static void tegra20_ac97_codec_reset(struct snd_ac97 *ac97)
+{
+ u32 readback;
+ unsigned long timeout;
+
+ /* reset line is not driven by DAC pad group, have to toggle GPIO */
+ gpio_set_value(workdata->reset_gpio, 0);
+ udelay(2);
+
+ gpio_set_value(workdata->reset_gpio, 1);
+ udelay(2);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ do {
+ regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
+ if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY)
+ break;
+ usleep_range(1000, 2000);
+ } while (!time_after(jiffies, timeout));
+}
+
+static void tegra20_ac97_codec_warm_reset(struct snd_ac97 *ac97)
+{
+ u32 readback;
+ unsigned long timeout;
+
+ /*
+ * although sync line is driven by the DAC pad group warm reset using
+ * the controller cmd is not working, have to toggle sync line
+ * manually.
+ */
+ gpio_request(workdata->sync_gpio, "codec-sync");
+
+ gpio_direction_output(workdata->sync_gpio, 1);
+
+ udelay(2);
+ gpio_set_value(workdata->sync_gpio, 0);
+ udelay(2);
+ gpio_free(workdata->sync_gpio);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ do {
+ regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
+ if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY)
+ break;
+ usleep_range(1000, 2000);
+ } while (!time_after(jiffies, timeout));
+}
+
+static unsigned short tegra20_ac97_codec_read(struct snd_ac97 *ac97_snd,
+ unsigned short reg)
+{
+ u32 readback;
+ unsigned long timeout;
+
+ regmap_write(workdata->regmap, TEGRA20_AC97_CMD,
+ (((reg | 0x80) << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) &
+ TEGRA20_AC97_CMD_CMD_ADDR_MASK) |
+ TEGRA20_AC97_CMD_BUSY);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ do {
+ regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
+ if (readback & TEGRA20_AC97_STATUS1_STA_VALID1)
+ break;
+ usleep_range(1000, 2000);
+ } while (!time_after(jiffies, timeout));
+
+ return ((readback & TEGRA20_AC97_STATUS1_STA_DATA1_MASK) >>
+ TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT);
+}
+
+static void tegra20_ac97_codec_write(struct snd_ac97 *ac97_snd,
+ unsigned short reg, unsigned short val)
+{
+ u32 readback;
+ unsigned long timeout;
+
+ regmap_write(workdata->regmap, TEGRA20_AC97_CMD,
+ ((reg << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) &
+ TEGRA20_AC97_CMD_CMD_ADDR_MASK) |
+ ((val << TEGRA20_AC97_CMD_CMD_DATA_SHIFT) &
+ TEGRA20_AC97_CMD_CMD_DATA_MASK) |
+ TEGRA20_AC97_CMD_BUSY);
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ do {
+ regmap_read(workdata->regmap, TEGRA20_AC97_CMD, &readback);
+ if (!(readback & TEGRA20_AC97_CMD_BUSY))
+ break;
+ usleep_range(1000, 2000);
+ } while (!time_after(jiffies, timeout));
+}
+
+static struct snd_ac97_bus_ops tegra20_ac97_ops = {
+ .read = tegra20_ac97_codec_read,
+ .write = tegra20_ac97_codec_write,
+ .reset = tegra20_ac97_codec_reset,
+ .warm_reset = tegra20_ac97_codec_warm_reset,
+};
+
+static inline void tegra20_ac97_start_playback(struct tegra20_ac97 *ac97)
+{
+ regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
+ TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN,
+ TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN);
+
+ regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL,
+ TEGRA20_AC97_CTRL_PCM_DAC_EN |
+ TEGRA20_AC97_CTRL_STM_EN,
+ TEGRA20_AC97_CTRL_PCM_DAC_EN |
+ TEGRA20_AC97_CTRL_STM_EN);
+}
+
+static inline void tegra20_ac97_stop_playback(struct tegra20_ac97 *ac97)
+{
+ regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
+ TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN, 0);
+
+ regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL,
+ TEGRA20_AC97_CTRL_PCM_DAC_EN, 0);
+}
+
+static inline void tegra20_ac97_start_capture(struct tegra20_ac97 *ac97)
+{
+ regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
+ TEGRA20_AC97_FIFO_SCR_REC_FULL_EN,
+ TEGRA20_AC97_FIFO_SCR_REC_FULL_EN);
+}
+
+static inline void tegra20_ac97_stop_capture(struct tegra20_ac97 *ac97)
+{
+ regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
+ TEGRA20_AC97_FIFO_SCR_REC_FULL_EN, 0);
+}
+
+static int tegra20_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tegra20_ac97_start_playback(ac97);
+ else
+ tegra20_ac97_start_capture(ac97);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tegra20_ac97_stop_playback(ac97);
+ else
+ tegra20_ac97_stop_capture(ac97);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops tegra20_ac97_dai_ops = {
+ .trigger = tegra20_ac97_trigger,
+};
+
+static int tegra20_ac97_probe(struct snd_soc_dai *dai)
+{
+ struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai);
+
+ dai->capture_dma_data = &ac97->capture_dma_data;
+ dai->playback_dma_data = &ac97->playback_dma_data;
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver tegra20_ac97_dai = {
+ .name = "tegra-ac97-pcm",
+ .probe = tegra20_ac97_probe,
+ .playback = {
+ .stream_name = "PCM Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "PCM Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra20_ac97_dai_ops,
+};
+
+static const struct snd_soc_component_driver tegra20_ac97_component = {
+ .name = DRV_NAME,
+ .legacy_dai_naming = 1,
+};
+
+static bool tegra20_ac97_wr_rd_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA20_AC97_CTRL:
+ case TEGRA20_AC97_CMD:
+ case TEGRA20_AC97_STATUS1:
+ case TEGRA20_AC97_FIFO1_SCR:
+ case TEGRA20_AC97_FIFO_TX1:
+ case TEGRA20_AC97_FIFO_RX1:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static bool tegra20_ac97_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA20_AC97_STATUS1:
+ case TEGRA20_AC97_FIFO1_SCR:
+ case TEGRA20_AC97_FIFO_TX1:
+ case TEGRA20_AC97_FIFO_RX1:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static bool tegra20_ac97_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA20_AC97_FIFO_TX1:
+ case TEGRA20_AC97_FIFO_RX1:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static const struct regmap_config tegra20_ac97_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA20_AC97_FIFO_RX1,
+ .writeable_reg = tegra20_ac97_wr_rd_reg,
+ .readable_reg = tegra20_ac97_wr_rd_reg,
+ .volatile_reg = tegra20_ac97_volatile_reg,
+ .precious_reg = tegra20_ac97_precious_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int tegra20_ac97_platform_probe(struct platform_device *pdev)
+{
+ struct tegra20_ac97 *ac97;
+ struct resource *mem;
+ void __iomem *regs;
+ int ret = 0;
+
+ ac97 = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_ac97),
+ GFP_KERNEL);
+ if (!ac97) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ dev_set_drvdata(&pdev->dev, ac97);
+
+ ac97->reset = devm_reset_control_get_exclusive(&pdev->dev, "ac97");
+ if (IS_ERR(ac97->reset)) {
+ dev_err(&pdev->dev, "Can't retrieve ac97 reset\n");
+ return PTR_ERR(ac97->reset);
+ }
+
+ ac97->clk_ac97 = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(ac97->clk_ac97)) {
+ dev_err(&pdev->dev, "Can't retrieve ac97 clock\n");
+ ret = PTR_ERR(ac97->clk_ac97);
+ goto err;
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(regs)) {
+ ret = PTR_ERR(regs);
+ goto err_clk_put;
+ }
+
+ ac97->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
+ &tegra20_ac97_regmap_config);
+ if (IS_ERR(ac97->regmap)) {
+ dev_err(&pdev->dev, "regmap init failed\n");
+ ret = PTR_ERR(ac97->regmap);
+ goto err_clk_put;
+ }
+
+ ac97->reset_gpio = of_get_named_gpio(pdev->dev.of_node,
+ "nvidia,codec-reset-gpio", 0);
+ if (gpio_is_valid(ac97->reset_gpio)) {
+ ret = devm_gpio_request_one(&pdev->dev, ac97->reset_gpio,
+ GPIOF_OUT_INIT_HIGH, "codec-reset");
+ if (ret) {
+ dev_err(&pdev->dev, "could not get codec-reset GPIO\n");
+ goto err_clk_put;
+ }
+ } else {
+ dev_err(&pdev->dev, "no codec-reset GPIO supplied\n");
+ ret = -EINVAL;
+ goto err_clk_put;
+ }
+
+ ac97->sync_gpio = of_get_named_gpio(pdev->dev.of_node,
+ "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;
+ }
+
+ ac97->capture_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_RX1;
+ ac97->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ ac97->capture_dma_data.maxburst = 4;
+
+ ac97->playback_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_TX1;
+ ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ ac97->playback_dma_data.maxburst = 4;
+
+ ret = reset_control_assert(ac97->reset);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to assert AC'97 reset: %d\n", ret);
+ goto err_clk_put;
+ }
+
+ ret = clk_prepare_enable(ac97->clk_ac97);
+ if (ret) {
+ dev_err(&pdev->dev, "clk_enable failed: %d\n", ret);
+ goto err_clk_put;
+ }
+
+ usleep_range(10, 100);
+
+ ret = reset_control_deassert(ac97->reset);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to deassert AC'97 reset: %d\n", ret);
+ goto err_clk_disable_unprepare;
+ }
+
+ ret = snd_soc_set_ac97_ops(&tegra20_ac97_ops);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret);
+ goto err_clk_disable_unprepare;
+ }
+
+ ret = snd_soc_register_component(&pdev->dev, &tegra20_ac97_component,
+ &tegra20_ac97_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
+ ret = -ENOMEM;
+ goto err_clk_disable_unprepare;
+ }
+
+ ret = tegra_pcm_platform_register(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
+ goto err_unregister_component;
+ }
+
+ /* XXX: crufty ASoC AC97 API - only one AC97 codec allowed */
+ workdata = ac97;
+
+ return 0;
+
+err_unregister_component:
+ snd_soc_unregister_component(&pdev->dev);
+err_clk_disable_unprepare:
+ clk_disable_unprepare(ac97->clk_ac97);
+err_clk_put:
+err:
+ snd_soc_set_ac97_ops(NULL);
+ return ret;
+}
+
+static int tegra20_ac97_platform_remove(struct platform_device *pdev)
+{
+ struct tegra20_ac97 *ac97 = dev_get_drvdata(&pdev->dev);
+
+ tegra_pcm_platform_unregister(&pdev->dev);
+ snd_soc_unregister_component(&pdev->dev);
+
+ clk_disable_unprepare(ac97->clk_ac97);
+
+ snd_soc_set_ac97_ops(NULL);
+
+ return 0;
+}
+
+static const struct of_device_id tegra20_ac97_of_match[] = {
+ { .compatible = "nvidia,tegra20-ac97", },
+ {},
+};
+
+static struct platform_driver tegra20_ac97_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = tegra20_ac97_of_match,
+ },
+ .probe = tegra20_ac97_platform_probe,
+ .remove = tegra20_ac97_platform_remove,
+};
+module_platform_driver(tegra20_ac97_driver);
+
+MODULE_AUTHOR("Lucas Stach");
+MODULE_DESCRIPTION("Tegra20 AC97 ASoC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_DEVICE_TABLE(of, tegra20_ac97_of_match);
diff --git a/sound/soc/tegra/tegra20_ac97.h b/sound/soc/tegra/tegra20_ac97.h
new file mode 100644
index 000000000..870ea09ff
--- /dev/null
+++ b/sound/soc/tegra/tegra20_ac97.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra20_ac97.h - Definitions for the Tegra20 AC97 controller driver
+ *
+ * Copyright (c) 2012 Lucas Stach <dev@lynxeye.de>
+ *
+ * Partly based on code copyright/by:
+ *
+ * Copyright (c) 2011,2012 Toradex Inc.
+ */
+
+#ifndef __TEGRA20_AC97_H__
+#define __TEGRA20_AC97_H__
+
+#include "tegra_pcm.h"
+
+#define TEGRA20_AC97_CTRL 0x00
+#define TEGRA20_AC97_CMD 0x04
+#define TEGRA20_AC97_STATUS1 0x08
+/* ... */
+#define TEGRA20_AC97_FIFO1_SCR 0x1c
+/* ... */
+#define TEGRA20_AC97_FIFO_TX1 0x40
+#define TEGRA20_AC97_FIFO_RX1 0x80
+
+/* TEGRA20_AC97_CTRL */
+#define TEGRA20_AC97_CTRL_STM2_EN (1 << 16)
+#define TEGRA20_AC97_CTRL_DOUBLE_SAMPLING_EN (1 << 11)
+#define TEGRA20_AC97_CTRL_IO_CNTRL_EN (1 << 10)
+#define TEGRA20_AC97_CTRL_HSET_DAC_EN (1 << 9)
+#define TEGRA20_AC97_CTRL_LINE2_DAC_EN (1 << 8)
+#define TEGRA20_AC97_CTRL_PCM_LFE_EN (1 << 7)
+#define TEGRA20_AC97_CTRL_PCM_SUR_EN (1 << 6)
+#define TEGRA20_AC97_CTRL_PCM_CEN_DAC_EN (1 << 5)
+#define TEGRA20_AC97_CTRL_LINE1_DAC_EN (1 << 4)
+#define TEGRA20_AC97_CTRL_PCM_DAC_EN (1 << 3)
+#define TEGRA20_AC97_CTRL_COLD_RESET (1 << 2)
+#define TEGRA20_AC97_CTRL_WARM_RESET (1 << 1)
+#define TEGRA20_AC97_CTRL_STM_EN (1 << 0)
+
+/* TEGRA20_AC97_CMD */
+#define TEGRA20_AC97_CMD_CMD_ADDR_SHIFT 24
+#define TEGRA20_AC97_CMD_CMD_ADDR_MASK (0xff << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT)
+#define TEGRA20_AC97_CMD_CMD_DATA_SHIFT 8
+#define TEGRA20_AC97_CMD_CMD_DATA_MASK (0xffff << TEGRA20_AC97_CMD_CMD_DATA_SHIFT)
+#define TEGRA20_AC97_CMD_CMD_ID_SHIFT 2
+#define TEGRA20_AC97_CMD_CMD_ID_MASK (0x3 << TEGRA20_AC97_CMD_CMD_ID_SHIFT)
+#define TEGRA20_AC97_CMD_BUSY (1 << 0)
+
+/* TEGRA20_AC97_STATUS1 */
+#define TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT 24
+#define TEGRA20_AC97_STATUS1_STA_ADDR1_MASK (0xff << TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT)
+#define TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT 8
+#define TEGRA20_AC97_STATUS1_STA_DATA1_MASK (0xffff << TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT)
+#define TEGRA20_AC97_STATUS1_STA_VALID1 (1 << 2)
+#define TEGRA20_AC97_STATUS1_STANDBY1 (1 << 1)
+#define TEGRA20_AC97_STATUS1_CODEC1_RDY (1 << 0)
+
+/* TEGRA20_AC97_FIFO1_SCR */
+#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT 27
+#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT)
+#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT 22
+#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT)
+#define TEGRA20_AC97_FIFO_SCR_REC_OVERRUN_INT_STA (1 << 19)
+#define TEGRA20_AC97_FIFO_SCR_PB_UNDERRUN_INT_STA (1 << 18)
+#define TEGRA20_AC97_FIFO_SCR_REC_FORCE_MT (1 << 17)
+#define TEGRA20_AC97_FIFO_SCR_PB_FORCE_MT (1 << 16)
+#define TEGRA20_AC97_FIFO_SCR_REC_FULL_EN (1 << 15)
+#define TEGRA20_AC97_FIFO_SCR_REC_3QRT_FULL_EN (1 << 14)
+#define TEGRA20_AC97_FIFO_SCR_REC_QRT_FULL_EN (1 << 13)
+#define TEGRA20_AC97_FIFO_SCR_REC_EMPTY_EN (1 << 12)
+#define TEGRA20_AC97_FIFO_SCR_PB_NOT_FULL_EN (1 << 11)
+#define TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN (1 << 10)
+#define TEGRA20_AC97_FIFO_SCR_PB_3QRT_MT_EN (1 << 9)
+#define TEGRA20_AC97_FIFO_SCR_PB_EMPTY_MT_EN (1 << 8)
+
+struct tegra20_ac97 {
+ struct clk *clk_ac97;
+ struct snd_dmaengine_dai_dma_data capture_dma_data;
+ struct snd_dmaengine_dai_dma_data playback_dma_data;
+ struct reset_control *reset;
+ struct regmap *regmap;
+ int reset_gpio;
+ int sync_gpio;
+};
+#endif /* __TEGRA20_AC97_H__ */
diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c
new file mode 100644
index 000000000..c620ab0c6
--- /dev/null
+++ b/sound/soc/tegra/tegra20_das.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tegra20_das.c - Tegra20 DAS driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+
+#define DRV_NAME "tegra20-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
+
+/*
+ * 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 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.
+ */
+
+static inline void tegra20_das_write(struct tegra20_das *das, u32 reg, u32 val)
+{
+ regmap_write(das->regmap, reg, val);
+}
+
+static void tegra20_das_connect_dap_to_dac(struct tegra20_das *das, int dap, int dac)
+{
+ u32 addr;
+ u32 reg;
+
+ addr = TEGRA20_DAS_DAP_CTRL_SEL +
+ (dap * TEGRA20_DAS_DAP_CTRL_SEL_STRIDE);
+ reg = dac << TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P;
+
+ tegra20_das_write(das, addr, reg);
+}
+
+static void tegra20_das_connect_dac_to_dap(struct tegra20_das *das, int dac, int dap)
+{
+ u32 addr;
+ u32 reg;
+
+ 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(das, addr, reg);
+}
+
+#define LAST_REG(name) \
+ (TEGRA20_DAS_##name + \
+ (TEGRA20_DAS_##name##_STRIDE * (TEGRA20_DAS_##name##_COUNT - 1)))
+
+static bool tegra20_das_wr_rd_reg(struct device *dev, unsigned int reg)
+{
+ if (reg <= LAST_REG(DAP_CTRL_SEL))
+ return true;
+ if ((reg >= TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL) &&
+ (reg <= LAST_REG(DAC_INPUT_DATA_CLK_SEL)))
+ return true;
+
+ return false;
+}
+
+static const struct regmap_config tegra20_das_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = LAST_REG(DAC_INPUT_DATA_CLK_SEL),
+ .writeable_reg = tegra20_das_wr_rd_reg,
+ .readable_reg = tegra20_das_wr_rd_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int tegra20_das_probe(struct platform_device *pdev)
+{
+ void __iomem *regs;
+ struct tegra20_das *das;
+
+ das = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_das), GFP_KERNEL);
+ if (!das)
+ return -ENOMEM;
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ 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");
+ return PTR_ERR(das->regmap);
+ }
+
+ 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;
+}
+
+static const struct of_device_id tegra20_das_of_match[] = {
+ { .compatible = "nvidia,tegra20-das", },
+ {},
+};
+
+static struct platform_driver tegra20_das_driver = {
+ .probe = tegra20_das_probe,
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = tegra20_das_of_match,
+ },
+};
+module_platform_driver(tegra20_das_driver);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra20 DAS driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_DEVICE_TABLE(of, tegra20_das_of_match);
diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c
new file mode 100644
index 000000000..fff0cd658
--- /dev/null
+++ b/sound/soc/tegra/tegra20_i2s.c
@@ -0,0 +1,514 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tegra20_i2s.c - Tegra20 I2S driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010,2012 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.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>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "tegra20_i2s.h"
+
+#define DRV_NAME "tegra20-i2s"
+
+static __maybe_unused int tegra20_i2s_runtime_suspend(struct device *dev)
+{
+ struct tegra20_i2s *i2s = dev_get_drvdata(dev);
+
+ regcache_cache_only(i2s->regmap, true);
+
+ clk_disable_unprepare(i2s->clk_i2s);
+
+ return 0;
+}
+
+static __maybe_unused int tegra20_i2s_runtime_resume(struct device *dev)
+{
+ struct tegra20_i2s *i2s = dev_get_drvdata(dev);
+ int ret;
+
+ ret = reset_control_assert(i2s->reset);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(i2s->clk_i2s);
+ if (ret) {
+ dev_err(dev, "clk_enable failed: %d\n", ret);
+ return ret;
+ }
+
+ usleep_range(10, 100);
+
+ ret = reset_control_deassert(i2s->reset);
+ if (ret)
+ goto disable_clocks;
+
+ regcache_cache_only(i2s->regmap, false);
+ regcache_mark_dirty(i2s->regmap);
+
+ ret = regcache_sync(i2s->regmap);
+ if (ret)
+ goto disable_clocks;
+
+ return 0;
+
+disable_clocks:
+ clk_disable_unprepare(i2s->clk_i2s);
+
+ return ret;
+}
+
+static int tegra20_i2s_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ unsigned int mask = 0, val = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mask |= TEGRA20_I2S_CTRL_MASTER_ENABLE;
+ 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_BC_FC:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mask |= TEGRA20_I2S_CTRL_BIT_FORMAT_MASK |
+ TEGRA20_I2S_CTRL_LRCK_MASK;
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ val |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP;
+ val |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ val |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP;
+ val |= TEGRA20_I2S_CTRL_LRCK_R_LOW;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val |= TEGRA20_I2S_CTRL_BIT_FORMAT_I2S;
+ val |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ val |= TEGRA20_I2S_CTRL_BIT_FORMAT_RJM;
+ val |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ val |= TEGRA20_I2S_CTRL_BIT_FORMAT_LJM;
+ val |= TEGRA20_I2S_CTRL_LRCK_L_LOW;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, mask, val);
+
+ return 0;
+}
+
+static int tegra20_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct device *dev = dai->dev;
+ struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ unsigned int mask, val;
+ int ret, sample_size, srate, i2sclock, bitcnt;
+
+ mask = TEGRA20_I2S_CTRL_BIT_SIZE_MASK;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ val = TEGRA20_I2S_CTRL_BIT_SIZE_16;
+ sample_size = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ val = TEGRA20_I2S_CTRL_BIT_SIZE_24;
+ sample_size = 24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ val = TEGRA20_I2S_CTRL_BIT_SIZE_32;
+ sample_size = 32;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mask |= TEGRA20_I2S_CTRL_FIFO_FORMAT_MASK;
+ val |= TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED;
+
+ regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, mask, val);
+
+ srate = params_rate(params);
+
+ /* Final "* 2" required by Tegra hardware */
+ i2sclock = srate * params_channels(params) * sample_size * 2;
+
+ ret = clk_set_rate(i2s->clk_i2s, i2sclock);
+ if (ret) {
+ dev_err(dev, "Can't set I2S clock rate: %d\n", ret);
+ return ret;
+ }
+
+ bitcnt = (i2sclock / (2 * srate)) - 1;
+ if (bitcnt < 0 || bitcnt > TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US)
+ return -EINVAL;
+ val = bitcnt << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT;
+
+ if (i2sclock % (2 * srate))
+ val |= TEGRA20_I2S_TIMING_NON_SYM_ENABLE;
+
+ regmap_write(i2s->regmap, TEGRA20_I2S_TIMING, val);
+
+ regmap_write(i2s->regmap, TEGRA20_I2S_FIFO_SCR,
+ TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS |
+ TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS);
+
+ return 0;
+}
+
+static void tegra20_i2s_start_playback(struct tegra20_i2s *i2s)
+{
+ regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL,
+ TEGRA20_I2S_CTRL_FIFO1_ENABLE,
+ TEGRA20_I2S_CTRL_FIFO1_ENABLE);
+}
+
+static void tegra20_i2s_stop_playback(struct tegra20_i2s *i2s)
+{
+ regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL,
+ TEGRA20_I2S_CTRL_FIFO1_ENABLE, 0);
+}
+
+static void tegra20_i2s_start_capture(struct tegra20_i2s *i2s)
+{
+ regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL,
+ TEGRA20_I2S_CTRL_FIFO2_ENABLE,
+ TEGRA20_I2S_CTRL_FIFO2_ENABLE);
+}
+
+static void tegra20_i2s_stop_capture(struct tegra20_i2s *i2s)
+{
+ regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL,
+ TEGRA20_I2S_CTRL_FIFO2_ENABLE, 0);
+}
+
+static int tegra20_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tegra20_i2s_start_playback(i2s);
+ else
+ tegra20_i2s_start_capture(i2s);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tegra20_i2s_stop_playback(i2s);
+ else
+ tegra20_i2s_stop_capture(i2s);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tegra20_i2s_probe(struct snd_soc_dai *dai)
+{
+ struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ dai->capture_dma_data = &i2s->capture_dma_data;
+ dai->playback_dma_data = &i2s->playback_dma_data;
+
+ 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 = {
+ .probe = tegra20_i2s_probe,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra20_i2s_dai_ops,
+ .symmetric_rate = 1,
+};
+
+static const struct snd_soc_component_driver tegra20_i2s_component = {
+ .name = DRV_NAME,
+ .legacy_dai_naming = 1,
+};
+
+static bool tegra20_i2s_wr_rd_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA20_I2S_CTRL:
+ case TEGRA20_I2S_STATUS:
+ case TEGRA20_I2S_TIMING:
+ case TEGRA20_I2S_FIFO_SCR:
+ case TEGRA20_I2S_PCM_CTRL:
+ case TEGRA20_I2S_NW_CTRL:
+ case TEGRA20_I2S_TDM_CTRL:
+ case TEGRA20_I2S_TDM_TX_RX_CTRL:
+ case TEGRA20_I2S_FIFO1:
+ case TEGRA20_I2S_FIFO2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra20_i2s_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA20_I2S_STATUS:
+ case TEGRA20_I2S_FIFO_SCR:
+ case TEGRA20_I2S_FIFO1:
+ case TEGRA20_I2S_FIFO2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra20_i2s_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA20_I2S_FIFO1:
+ case TEGRA20_I2S_FIFO2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tegra20_i2s_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA20_I2S_FIFO2,
+ .writeable_reg = tegra20_i2s_wr_rd_reg,
+ .readable_reg = tegra20_i2s_wr_rd_reg,
+ .volatile_reg = tegra20_i2s_volatile_reg,
+ .precious_reg = tegra20_i2s_precious_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int tegra20_i2s_platform_probe(struct platform_device *pdev)
+{
+ struct tegra20_i2s *i2s;
+ struct resource *mem;
+ void __iomem *regs;
+ int ret;
+
+ i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_i2s), GFP_KERNEL);
+ if (!i2s) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ dev_set_drvdata(&pdev->dev, i2s);
+
+ i2s->dai = tegra20_i2s_dai_template;
+ i2s->dai.name = dev_name(&pdev->dev);
+
+ i2s->reset = devm_reset_control_get_exclusive(&pdev->dev, "i2s");
+ if (IS_ERR(i2s->reset)) {
+ dev_err(&pdev->dev, "Can't retrieve i2s reset\n");
+ return PTR_ERR(i2s->reset);
+ }
+
+ i2s->clk_i2s = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(i2s->clk_i2s)) {
+ dev_err(&pdev->dev, "Can't retrieve i2s clock\n");
+ ret = PTR_ERR(i2s->clk_i2s);
+ goto err;
+ }
+
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
+ if (IS_ERR(regs)) {
+ ret = PTR_ERR(regs);
+ goto err;
+ }
+
+ i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
+ &tegra20_i2s_regmap_config);
+ if (IS_ERR(i2s->regmap)) {
+ dev_err(&pdev->dev, "regmap init failed\n");
+ ret = PTR_ERR(i2s->regmap);
+ goto err;
+ }
+
+ i2s->capture_dma_data.addr = mem->start + TEGRA20_I2S_FIFO2;
+ i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ i2s->capture_dma_data.maxburst = 4;
+
+ i2s->playback_dma_data.addr = mem->start + TEGRA20_I2S_FIFO1;
+ i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ i2s->playback_dma_data.maxburst = 4;
+
+ pm_runtime_enable(&pdev->dev);
+
+ ret = snd_soc_register_component(&pdev->dev, &tegra20_i2s_component,
+ &i2s->dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
+ ret = -ENOMEM;
+ goto err_pm_disable;
+ }
+
+ ret = tegra_pcm_platform_register(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
+ goto err_unregister_component;
+ }
+
+ return 0;
+
+err_unregister_component:
+ snd_soc_unregister_component(&pdev->dev);
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+err:
+ return ret;
+}
+
+static int tegra20_i2s_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 of_device_id tegra20_i2s_of_match[] = {
+ { .compatible = "nvidia,tegra20-i2s", },
+ {},
+};
+
+static const struct dev_pm_ops tegra20_i2s_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra20_i2s_runtime_suspend,
+ tegra20_i2s_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver tegra20_i2s_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = tegra20_i2s_of_match,
+ .pm = &tegra20_i2s_pm_ops,
+ },
+ .probe = tegra20_i2s_platform_probe,
+ .remove = tegra20_i2s_platform_remove,
+};
+module_platform_driver(tegra20_i2s_driver);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra20 I2S ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_DEVICE_TABLE(of, tegra20_i2s_of_match);
diff --git a/sound/soc/tegra/tegra20_i2s.h b/sound/soc/tegra/tegra20_i2s.h
new file mode 100644
index 000000000..8233e5fa2
--- /dev/null
+++ b/sound/soc/tegra/tegra20_i2s.h
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra20_i2s.h - Definitions for Tegra20 I2S driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010,2012 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ */
+
+#ifndef __TEGRA20_I2S_H__
+#define __TEGRA20_I2S_H__
+
+#include "tegra_pcm.h"
+
+/* Register offsets from TEGRA20_I2S1_BASE and TEGRA20_I2S2_BASE */
+
+#define TEGRA20_I2S_CTRL 0x00
+#define TEGRA20_I2S_STATUS 0x04
+#define TEGRA20_I2S_TIMING 0x08
+#define TEGRA20_I2S_FIFO_SCR 0x0c
+#define TEGRA20_I2S_PCM_CTRL 0x10
+#define TEGRA20_I2S_NW_CTRL 0x14
+#define TEGRA20_I2S_TDM_CTRL 0x20
+#define TEGRA20_I2S_TDM_TX_RX_CTRL 0x24
+#define TEGRA20_I2S_FIFO1 0x40
+#define TEGRA20_I2S_FIFO2 0x80
+
+/* Fields in TEGRA20_I2S_CTRL */
+
+#define TEGRA20_I2S_CTRL_FIFO2_TX_ENABLE (1 << 30)
+#define TEGRA20_I2S_CTRL_FIFO1_ENABLE (1 << 29)
+#define TEGRA20_I2S_CTRL_FIFO2_ENABLE (1 << 28)
+#define TEGRA20_I2S_CTRL_FIFO1_RX_ENABLE (1 << 27)
+#define TEGRA20_I2S_CTRL_FIFO_LPBK_ENABLE (1 << 26)
+#define TEGRA20_I2S_CTRL_MASTER_ENABLE (1 << 25)
+
+#define TEGRA20_I2S_LRCK_LEFT_LOW 0
+#define TEGRA20_I2S_LRCK_RIGHT_LOW 1
+
+#define TEGRA20_I2S_CTRL_LRCK_SHIFT 24
+#define TEGRA20_I2S_CTRL_LRCK_MASK (1 << TEGRA20_I2S_CTRL_LRCK_SHIFT)
+#define TEGRA20_I2S_CTRL_LRCK_L_LOW (TEGRA20_I2S_LRCK_LEFT_LOW << TEGRA20_I2S_CTRL_LRCK_SHIFT)
+#define TEGRA20_I2S_CTRL_LRCK_R_LOW (TEGRA20_I2S_LRCK_RIGHT_LOW << TEGRA20_I2S_CTRL_LRCK_SHIFT)
+
+#define TEGRA20_I2S_BIT_FORMAT_I2S 0
+#define TEGRA20_I2S_BIT_FORMAT_RJM 1
+#define TEGRA20_I2S_BIT_FORMAT_LJM 2
+#define TEGRA20_I2S_BIT_FORMAT_DSP 3
+
+#define TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT 10
+#define TEGRA20_I2S_CTRL_BIT_FORMAT_MASK (3 << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA20_I2S_CTRL_BIT_FORMAT_I2S (TEGRA20_I2S_BIT_FORMAT_I2S << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA20_I2S_CTRL_BIT_FORMAT_RJM (TEGRA20_I2S_BIT_FORMAT_RJM << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA20_I2S_CTRL_BIT_FORMAT_LJM (TEGRA20_I2S_BIT_FORMAT_LJM << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA20_I2S_CTRL_BIT_FORMAT_DSP (TEGRA20_I2S_BIT_FORMAT_DSP << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT)
+
+#define TEGRA20_I2S_BIT_SIZE_16 0
+#define TEGRA20_I2S_BIT_SIZE_20 1
+#define TEGRA20_I2S_BIT_SIZE_24 2
+#define TEGRA20_I2S_BIT_SIZE_32 3
+
+#define TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT 8
+#define TEGRA20_I2S_CTRL_BIT_SIZE_MASK (3 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA20_I2S_CTRL_BIT_SIZE_16 (TEGRA20_I2S_BIT_SIZE_16 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA20_I2S_CTRL_BIT_SIZE_20 (TEGRA20_I2S_BIT_SIZE_20 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA20_I2S_CTRL_BIT_SIZE_24 (TEGRA20_I2S_BIT_SIZE_24 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA20_I2S_CTRL_BIT_SIZE_32 (TEGRA20_I2S_BIT_SIZE_32 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT)
+
+#define TEGRA20_I2S_FIFO_16_LSB 0
+#define TEGRA20_I2S_FIFO_20_LSB 1
+#define TEGRA20_I2S_FIFO_24_LSB 2
+#define TEGRA20_I2S_FIFO_32 3
+#define TEGRA20_I2S_FIFO_PACKED 7
+
+#define TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT 4
+#define TEGRA20_I2S_CTRL_FIFO_FORMAT_MASK (7 << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA20_I2S_CTRL_FIFO_FORMAT_16_LSB (TEGRA20_I2S_FIFO_16_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA20_I2S_CTRL_FIFO_FORMAT_20_LSB (TEGRA20_I2S_FIFO_20_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA20_I2S_CTRL_FIFO_FORMAT_24_LSB (TEGRA20_I2S_FIFO_24_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA20_I2S_CTRL_FIFO_FORMAT_32 (TEGRA20_I2S_FIFO_32 << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED (TEGRA20_I2S_FIFO_PACKED << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT)
+
+#define TEGRA20_I2S_CTRL_IE_FIFO1_ERR (1 << 3)
+#define TEGRA20_I2S_CTRL_IE_FIFO2_ERR (1 << 2)
+#define TEGRA20_I2S_CTRL_QE_FIFO1 (1 << 1)
+#define TEGRA20_I2S_CTRL_QE_FIFO2 (1 << 0)
+
+/* Fields in TEGRA20_I2S_STATUS */
+
+#define TEGRA20_I2S_STATUS_FIFO1_RDY (1 << 31)
+#define TEGRA20_I2S_STATUS_FIFO2_RDY (1 << 30)
+#define TEGRA20_I2S_STATUS_FIFO1_BSY (1 << 29)
+#define TEGRA20_I2S_STATUS_FIFO2_BSY (1 << 28)
+#define TEGRA20_I2S_STATUS_FIFO1_ERR (1 << 3)
+#define TEGRA20_I2S_STATUS_FIFO2_ERR (1 << 2)
+#define TEGRA20_I2S_STATUS_QS_FIFO1 (1 << 1)
+#define TEGRA20_I2S_STATUS_QS_FIFO2 (1 << 0)
+
+/* Fields in TEGRA20_I2S_TIMING */
+
+#define TEGRA20_I2S_TIMING_NON_SYM_ENABLE (1 << 12)
+#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0
+#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7ff
+#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT)
+
+/* Fields in TEGRA20_I2S_FIFO_SCR */
+
+#define TEGRA20_I2S_FIFO_SCR_FIFO2_FULL_EMPTY_COUNT_SHIFT 24
+#define TEGRA20_I2S_FIFO_SCR_FIFO1_FULL_EMPTY_COUNT_SHIFT 16
+#define TEGRA20_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK 0x3f
+
+#define TEGRA20_I2S_FIFO_SCR_FIFO2_CLR (1 << 12)
+#define TEGRA20_I2S_FIFO_SCR_FIFO1_CLR (1 << 8)
+
+#define TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT 0
+#define TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS 1
+#define TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS 2
+#define TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS 3
+
+#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT 4
+#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_MASK (3 << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_ONE_SLOT (TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_EIGHT_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_TWELVE_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+
+#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT 0
+#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_MASK (3 << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_ONE_SLOT (TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_EIGHT_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+
+struct tegra20_i2s {
+ struct snd_soc_dai_driver dai;
+ struct clk *clk_i2s;
+ 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/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c
new file mode 100644
index 000000000..ca7b222e0
--- /dev/null
+++ b/sound/soc/tegra/tegra20_spdif.c
@@ -0,0 +1,432 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tegra20_spdif.c - Tegra20 SPDIF driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2011-2012 - NVIDIA, Inc.
+ */
+
+#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>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "tegra20_spdif.h"
+
+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;
+}
+
+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 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;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ val |= TEGRA20_SPDIF_CTRL_PACK |
+ TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ 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;
+ break;
+ case 44100:
+ spdifclock = 5644800;
+ break;
+ case 48000:
+ spdifclock = 6144000;
+ break;
+ case 88200:
+ spdifclock = 11289600;
+ break;
+ case 96000:
+ spdifclock = 12288000;
+ break;
+ case 176400:
+ spdifclock = 22579200;
+ break;
+ case 192000:
+ spdifclock = 24576000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = clk_set_rate(spdif->clk_spdif_out, spdifclock);
+ if (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;
+}
+
+static void tegra20_spdif_start_playback(struct tegra20_spdif *spdif)
+{
+ regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL,
+ TEGRA20_SPDIF_CTRL_TX_EN,
+ TEGRA20_SPDIF_CTRL_TX_EN);
+}
+
+static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif)
+{
+ regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL,
+ TEGRA20_SPDIF_CTRL_TX_EN, 0);
+}
+
+static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ tegra20_spdif_start_playback(spdif);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ tegra20_spdif_stop_playback(spdif);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ 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 = dev_get_drvdata(dai->dev);
+
+ dai->capture_dma_data = NULL;
+ dai->playback_dma_data = &spdif->playback_dma_data;
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = {
+ .hw_params = tegra20_spdif_hw_params,
+ .trigger = tegra20_spdif_trigger,
+ .startup = tegra20_spdif_startup,
+};
+
+static struct snd_soc_dai_driver tegra20_spdif_dai = {
+ .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,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra20_spdif_dai_ops,
+};
+
+static const struct snd_soc_component_driver tegra20_spdif_component = {
+ .name = "tegra20-spdif",
+ .legacy_dai_naming = 1,
+};
+
+static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA20_SPDIF_CTRL:
+ case TEGRA20_SPDIF_STATUS:
+ case TEGRA20_SPDIF_STROBE_CTRL:
+ case TEGRA20_SPDIF_DATA_FIFO_CSR:
+ case TEGRA20_SPDIF_DATA_OUT:
+ case TEGRA20_SPDIF_DATA_IN:
+ case TEGRA20_SPDIF_CH_STA_RX_A:
+ case TEGRA20_SPDIF_CH_STA_RX_B:
+ case TEGRA20_SPDIF_CH_STA_RX_C:
+ case TEGRA20_SPDIF_CH_STA_RX_D:
+ case TEGRA20_SPDIF_CH_STA_RX_E:
+ case TEGRA20_SPDIF_CH_STA_RX_F:
+ case TEGRA20_SPDIF_CH_STA_TX_A:
+ case TEGRA20_SPDIF_CH_STA_TX_B:
+ case TEGRA20_SPDIF_CH_STA_TX_C:
+ case TEGRA20_SPDIF_CH_STA_TX_D:
+ case TEGRA20_SPDIF_CH_STA_TX_E:
+ case TEGRA20_SPDIF_CH_STA_TX_F:
+ case TEGRA20_SPDIF_USR_STA_RX_A:
+ case TEGRA20_SPDIF_USR_DAT_TX_A:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra20_spdif_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA20_SPDIF_STATUS:
+ case TEGRA20_SPDIF_DATA_FIFO_CSR:
+ case TEGRA20_SPDIF_DATA_OUT:
+ case TEGRA20_SPDIF_DATA_IN:
+ case TEGRA20_SPDIF_CH_STA_RX_A:
+ case TEGRA20_SPDIF_CH_STA_RX_B:
+ case TEGRA20_SPDIF_CH_STA_RX_C:
+ case TEGRA20_SPDIF_CH_STA_RX_D:
+ case TEGRA20_SPDIF_CH_STA_RX_E:
+ case TEGRA20_SPDIF_CH_STA_RX_F:
+ case TEGRA20_SPDIF_USR_STA_RX_A:
+ case TEGRA20_SPDIF_USR_DAT_TX_A:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra20_spdif_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA20_SPDIF_DATA_OUT:
+ case TEGRA20_SPDIF_DATA_IN:
+ case TEGRA20_SPDIF_USR_STA_RX_A:
+ case TEGRA20_SPDIF_USR_DAT_TX_A:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tegra20_spdif_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA20_SPDIF_USR_DAT_TX_A,
+ .writeable_reg = tegra20_spdif_wr_rd_reg,
+ .readable_reg = tegra20_spdif_wr_rd_reg,
+ .volatile_reg = tegra20_spdif_volatile_reg,
+ .precious_reg = tegra20_spdif_precious_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int tegra20_spdif_platform_probe(struct platform_device *pdev)
+{
+ struct tegra20_spdif *spdif;
+ struct resource *mem;
+ void __iomem *regs;
+ int ret;
+
+ spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif),
+ GFP_KERNEL);
+ if (!spdif)
+ return -ENOMEM;
+
+ dev_set_drvdata(&pdev->dev, spdif);
+
+ 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)) {
+ 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);
+
+ spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
+ &tegra20_spdif_regmap_config);
+ if (IS_ERR(spdif->regmap)) {
+ dev_err(&pdev->dev, "regmap init failed\n");
+ 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;
+
+ ret = devm_pm_runtime_enable(&pdev->dev);
+ if (ret)
+ return ret;
+
+ 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);
+ return ret;
+ }
+
+ ret = devm_tegra_pcm_platform_register(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
+ return ret;
+ }
+
+ 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 = "tegra20-spdif",
+ .pm = &tegra20_spdif_pm_ops,
+ .of_match_table = tegra20_spdif_of_match,
+ },
+ .probe = tegra20_spdif_platform_probe,
+};
+module_platform_driver(tegra20_spdif_driver);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/tegra/tegra20_spdif.h b/sound/soc/tegra/tegra20_spdif.h
new file mode 100644
index 000000000..ff4b79e20
--- /dev/null
+++ b/sound/soc/tegra/tegra20_spdif.h
@@ -0,0 +1,457 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra20_spdif.h - Definitions for Tegra20 SPDIF driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2011 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ * Copyright (c) 2008-2009, NVIDIA Corporation
+ */
+
+#ifndef __TEGRA20_SPDIF_H__
+#define __TEGRA20_SPDIF_H__
+
+#include "tegra_pcm.h"
+
+/* Offsets from TEGRA20_SPDIF_BASE */
+
+#define TEGRA20_SPDIF_CTRL 0x0
+#define TEGRA20_SPDIF_STATUS 0x4
+#define TEGRA20_SPDIF_STROBE_CTRL 0x8
+#define TEGRA20_SPDIF_DATA_FIFO_CSR 0x0C
+#define TEGRA20_SPDIF_DATA_OUT 0x40
+#define TEGRA20_SPDIF_DATA_IN 0x80
+#define TEGRA20_SPDIF_CH_STA_RX_A 0x100
+#define TEGRA20_SPDIF_CH_STA_RX_B 0x104
+#define TEGRA20_SPDIF_CH_STA_RX_C 0x108
+#define TEGRA20_SPDIF_CH_STA_RX_D 0x10C
+#define TEGRA20_SPDIF_CH_STA_RX_E 0x110
+#define TEGRA20_SPDIF_CH_STA_RX_F 0x114
+#define TEGRA20_SPDIF_CH_STA_TX_A 0x140
+#define TEGRA20_SPDIF_CH_STA_TX_B 0x144
+#define TEGRA20_SPDIF_CH_STA_TX_C 0x148
+#define TEGRA20_SPDIF_CH_STA_TX_D 0x14C
+#define TEGRA20_SPDIF_CH_STA_TX_E 0x150
+#define TEGRA20_SPDIF_CH_STA_TX_F 0x154
+#define TEGRA20_SPDIF_USR_STA_RX_A 0x180
+#define TEGRA20_SPDIF_USR_DAT_TX_A 0x1C0
+
+/* Fields in TEGRA20_SPDIF_CTRL */
+
+/* Start capturing from 0=right, 1=left channel */
+#define TEGRA20_SPDIF_CTRL_CAP_LC (1 << 30)
+
+/* SPDIF receiver(RX) enable */
+#define TEGRA20_SPDIF_CTRL_RX_EN (1 << 29)
+
+/* SPDIF Transmitter(TX) enable */
+#define TEGRA20_SPDIF_CTRL_TX_EN (1 << 28)
+
+/* Transmit Channel status */
+#define TEGRA20_SPDIF_CTRL_TC_EN (1 << 27)
+
+/* Transmit user Data */
+#define TEGRA20_SPDIF_CTRL_TU_EN (1 << 26)
+
+/* Interrupt on transmit error */
+#define TEGRA20_SPDIF_CTRL_IE_TXE (1 << 25)
+
+/* Interrupt on receive error */
+#define TEGRA20_SPDIF_CTRL_IE_RXE (1 << 24)
+
+/* Interrupt on invalid preamble */
+#define TEGRA20_SPDIF_CTRL_IE_P (1 << 23)
+
+/* Interrupt on "B" preamble */
+#define TEGRA20_SPDIF_CTRL_IE_B (1 << 22)
+
+/* Interrupt when block of channel status received */
+#define TEGRA20_SPDIF_CTRL_IE_C (1 << 21)
+
+/* Interrupt when a valid information unit (IU) is received */
+#define TEGRA20_SPDIF_CTRL_IE_U (1 << 20)
+
+/* Interrupt when RX user FIFO attention level is reached */
+#define TEGRA20_SPDIF_CTRL_QE_RU (1 << 19)
+
+/* Interrupt when TX user FIFO attention level is reached */
+#define TEGRA20_SPDIF_CTRL_QE_TU (1 << 18)
+
+/* Interrupt when RX data FIFO attention level is reached */
+#define TEGRA20_SPDIF_CTRL_QE_RX (1 << 17)
+
+/* Interrupt when TX data FIFO attention level is reached */
+#define TEGRA20_SPDIF_CTRL_QE_TX (1 << 16)
+
+/* Loopback test mode enable */
+#define TEGRA20_SPDIF_CTRL_LBK_EN (1 << 15)
+
+/*
+ * Pack data mode:
+ * 0 = Single data (16 bit needs to be padded to match the
+ * interface data bit size).
+ * 1 = Packeted left/right channel data into a single word.
+ */
+#define TEGRA20_SPDIF_CTRL_PACK (1 << 14)
+
+/*
+ * 00 = 16bit data
+ * 01 = 20bit data
+ * 10 = 24bit data
+ * 11 = raw data
+ */
+#define TEGRA20_SPDIF_BIT_MODE_16BIT 0
+#define TEGRA20_SPDIF_BIT_MODE_20BIT 1
+#define TEGRA20_SPDIF_BIT_MODE_24BIT 2
+#define TEGRA20_SPDIF_BIT_MODE_RAW 3
+
+#define TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT 12
+#define TEGRA20_SPDIF_CTRL_BIT_MODE_MASK (3 << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT)
+#define TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT (TEGRA20_SPDIF_BIT_MODE_16BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT)
+#define TEGRA20_SPDIF_CTRL_BIT_MODE_20BIT (TEGRA20_SPDIF_BIT_MODE_20BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT)
+#define TEGRA20_SPDIF_CTRL_BIT_MODE_24BIT (TEGRA20_SPDIF_BIT_MODE_24BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT)
+#define TEGRA20_SPDIF_CTRL_BIT_MODE_RAW (TEGRA20_SPDIF_BIT_MODE_RAW << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT)
+
+/* Fields in TEGRA20_SPDIF_STATUS */
+
+/*
+ * Note: IS_P, IS_B, IS_C, and IS_U are sticky bits. Software must
+ * write a 1 to the corresponding bit location to clear the status.
+ */
+
+/*
+ * Receiver(RX) shifter is busy receiving data.
+ * This bit is asserted when the receiver first locked onto the
+ * preamble of the data stream after RX_EN is asserted. This bit is
+ * deasserted when either,
+ * (a) the end of a frame is reached after RX_EN is deeasserted, or
+ * (b) the SPDIF data stream becomes inactive.
+ */
+#define TEGRA20_SPDIF_STATUS_RX_BSY (1 << 29)
+
+/*
+ * Transmitter(TX) shifter is busy transmitting data.
+ * This bit is asserted when TX_EN is asserted.
+ * This bit is deasserted when the end of a frame is reached after
+ * TX_EN is deasserted.
+ */
+#define TEGRA20_SPDIF_STATUS_TX_BSY (1 << 28)
+
+/*
+ * TX is busy shifting out channel status.
+ * This bit is asserted when both TX_EN and TC_EN are asserted and
+ * data from CH_STA_TX_A register is loaded into the internal shifter.
+ * This bit is deasserted when either,
+ * (a) the end of a frame is reached after TX_EN is deasserted, or
+ * (b) CH_STA_TX_F register is loaded into the internal shifter.
+ */
+#define TEGRA20_SPDIF_STATUS_TC_BSY (1 << 27)
+
+/*
+ * TX User data FIFO busy.
+ * This bit is asserted when TX_EN and TXU_EN are asserted and
+ * there's data in the TX user FIFO. This bit is deassert when either,
+ * (a) the end of a frame is reached after TX_EN is deasserted, or
+ * (b) there's no data left in the TX user FIFO.
+ */
+#define TEGRA20_SPDIF_STATUS_TU_BSY (1 << 26)
+
+/* TX FIFO Underrun error status */
+#define TEGRA20_SPDIF_STATUS_TX_ERR (1 << 25)
+
+/* RX FIFO Overrun error status */
+#define TEGRA20_SPDIF_STATUS_RX_ERR (1 << 24)
+
+/* Preamble status: 0=Preamble OK, 1=bad/missing preamble */
+#define TEGRA20_SPDIF_STATUS_IS_P (1 << 23)
+
+/* B-preamble detection status: 0=not detected, 1=B-preamble detected */
+#define TEGRA20_SPDIF_STATUS_IS_B (1 << 22)
+
+/*
+ * RX channel block data receive status:
+ * 0=entire block not recieved yet.
+ * 1=received entire block of channel status,
+ */
+#define TEGRA20_SPDIF_STATUS_IS_C (1 << 21)
+
+/* RX User Data Valid flag: 1=valid IU detected, 0 = no IU detected. */
+#define TEGRA20_SPDIF_STATUS_IS_U (1 << 20)
+
+/*
+ * RX User FIFO Status:
+ * 1=attention level reached, 0=attention level not reached.
+ */
+#define TEGRA20_SPDIF_STATUS_QS_RU (1 << 19)
+
+/*
+ * TX User FIFO Status:
+ * 1=attention level reached, 0=attention level not reached.
+ */
+#define TEGRA20_SPDIF_STATUS_QS_TU (1 << 18)
+
+/*
+ * RX Data FIFO Status:
+ * 1=attention level reached, 0=attention level not reached.
+ */
+#define TEGRA20_SPDIF_STATUS_QS_RX (1 << 17)
+
+/*
+ * TX Data FIFO Status:
+ * 1=attention level reached, 0=attention level not reached.
+ */
+#define TEGRA20_SPDIF_STATUS_QS_TX (1 << 16)
+
+/* Fields in TEGRA20_SPDIF_STROBE_CTRL */
+
+/*
+ * Indicates the approximate number of detected SPDIFIN clocks within a
+ * bi-phase period.
+ */
+#define TEGRA20_SPDIF_STROBE_CTRL_PERIOD_SHIFT 16
+#define TEGRA20_SPDIF_STROBE_CTRL_PERIOD_MASK (0xff << TEGRA20_SPDIF_STROBE_CTRL_PERIOD_SHIFT)
+
+/* Data strobe mode: 0=Auto-locked 1=Manual locked */
+#define TEGRA20_SPDIF_STROBE_CTRL_STROBE (1 << 15)
+
+/*
+ * Manual data strobe time within the bi-phase clock period (in terms of
+ * the number of over-sampling clocks).
+ */
+#define TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT 8
+#define TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_MASK (0x1f << TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT)
+
+/*
+ * Manual SPDIFIN bi-phase clock period (in terms of the number of
+ * over-sampling clocks).
+ */
+#define TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT 0
+#define TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_MASK (0x3f << TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT)
+
+/* Fields in SPDIF_DATA_FIFO_CSR */
+
+/* Clear Receiver User FIFO (RX USR.FIFO) */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_CLR (1 << 31)
+
+#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT 0
+#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS 1
+#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS 2
+#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS 3
+
+/* RU FIFO attention level */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT 29
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_MASK \
+ (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU1_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU2_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU3_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU4_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT)
+
+/* Number of RX USR.FIFO levels with valid data. */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT 24
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_MASK (0x1f << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT)
+
+/* Clear Transmitter User FIFO (TX USR.FIFO) */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_CLR (1 << 23)
+
+/* TU FIFO attention level */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT 21
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_MASK \
+ (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU1_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU2_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU3_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU4_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT)
+
+/* Number of TX USR.FIFO levels that could be filled. */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT 16
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT)
+
+/* Clear Receiver Data FIFO (RX DATA.FIFO) */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_CLR (1 << 15)
+
+#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT 0
+#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS 1
+#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS 2
+#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS 3
+
+/* RU FIFO attention level */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT 13
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_MASK \
+ (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU1_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU4_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU8_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU12_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT)
+
+/* Number of RX DATA.FIFO levels with valid data. */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT 8
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_MASK (0x1f << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT)
+
+/* Clear Transmitter Data FIFO (TX DATA.FIFO) */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_CLR (1 << 7)
+
+/* TU FIFO attention level */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT 5
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK \
+ (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU1_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU8_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU12_WORD_FULL \
+ (TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT)
+
+/* Number of TX DATA.FIFO levels that could be filled. */
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT 0
+#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT)
+
+/* Fields in TEGRA20_SPDIF_DATA_OUT */
+
+/*
+ * This register has 5 different formats:
+ * 16-bit (BIT_MODE=00, PACK=0)
+ * 20-bit (BIT_MODE=01, PACK=0)
+ * 24-bit (BIT_MODE=10, PACK=0)
+ * raw (BIT_MODE=11, PACK=0)
+ * 16-bit packed (BIT_MODE=00, PACK=1)
+ */
+
+#define TEGRA20_SPDIF_DATA_OUT_DATA_16_SHIFT 0
+#define TEGRA20_SPDIF_DATA_OUT_DATA_16_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_OUT_DATA_20_SHIFT 0
+#define TEGRA20_SPDIF_DATA_OUT_DATA_20_MASK (0xfffff << TEGRA20_SPDIF_DATA_OUT_DATA_20_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_OUT_DATA_24_SHIFT 0
+#define TEGRA20_SPDIF_DATA_OUT_DATA_24_MASK (0xffffff << TEGRA20_SPDIF_DATA_OUT_DATA_24_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_P (1 << 31)
+#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_C (1 << 30)
+#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_U (1 << 29)
+#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_V (1 << 28)
+
+#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT 8
+#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_MASK (0xfffff << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT 4
+#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_MASK (0xf << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT 0
+#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT 16
+#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT 0
+#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT)
+
+/* Fields in TEGRA20_SPDIF_DATA_IN */
+
+/*
+ * This register has 5 different formats:
+ * 16-bit (BIT_MODE=00, PACK=0)
+ * 20-bit (BIT_MODE=01, PACK=0)
+ * 24-bit (BIT_MODE=10, PACK=0)
+ * raw (BIT_MODE=11, PACK=0)
+ * 16-bit packed (BIT_MODE=00, PACK=1)
+ *
+ * Bits 31:24 are common to all modes except 16-bit packed
+ */
+
+#define TEGRA20_SPDIF_DATA_IN_DATA_P (1 << 31)
+#define TEGRA20_SPDIF_DATA_IN_DATA_C (1 << 30)
+#define TEGRA20_SPDIF_DATA_IN_DATA_U (1 << 29)
+#define TEGRA20_SPDIF_DATA_IN_DATA_V (1 << 28)
+
+#define TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT 24
+#define TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_IN_DATA_16_SHIFT 0
+#define TEGRA20_SPDIF_DATA_IN_DATA_16_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_IN_DATA_20_SHIFT 0
+#define TEGRA20_SPDIF_DATA_IN_DATA_20_MASK (0xfffff << TEGRA20_SPDIF_DATA_IN_DATA_20_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_IN_DATA_24_SHIFT 0
+#define TEGRA20_SPDIF_DATA_IN_DATA_24_MASK (0xffffff << TEGRA20_SPDIF_DATA_IN_DATA_24_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT 8
+#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_MASK (0xfffff << TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT 4
+#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT 0
+#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT 16
+#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT)
+
+#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT 0
+#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT)
+
+/* Fields in TEGRA20_SPDIF_CH_STA_RX_A */
+/* Fields in TEGRA20_SPDIF_CH_STA_RX_B */
+/* Fields in TEGRA20_SPDIF_CH_STA_RX_C */
+/* Fields in TEGRA20_SPDIF_CH_STA_RX_D */
+/* Fields in TEGRA20_SPDIF_CH_STA_RX_E */
+/* Fields in TEGRA20_SPDIF_CH_STA_RX_F */
+
+/*
+ * The 6-word receive channel data page buffer holds a block (192 frames) of
+ * channel status information. The order of receive is from LSB to MSB
+ * bit, and from CH_STA_RX_A to CH_STA_RX_F then back to CH_STA_RX_A.
+ */
+
+/* Fields in TEGRA20_SPDIF_CH_STA_TX_A */
+/* Fields in TEGRA20_SPDIF_CH_STA_TX_B */
+/* Fields in TEGRA20_SPDIF_CH_STA_TX_C */
+/* Fields in TEGRA20_SPDIF_CH_STA_TX_D */
+/* Fields in TEGRA20_SPDIF_CH_STA_TX_E */
+/* Fields in TEGRA20_SPDIF_CH_STA_TX_F */
+
+/*
+ * The 6-word transmit channel data page buffer holds a block (192 frames) of
+ * channel status information. The order of transmission is from LSB to MSB
+ * bit, and from CH_STA_TX_A to CH_STA_TX_F then back to CH_STA_TX_A.
+ */
+
+/* Fields in TEGRA20_SPDIF_USR_STA_RX_A */
+
+/*
+ * This 4-word deep FIFO receives user FIFO field information. The order of
+ * receive is from LSB to MSB bit.
+ */
+
+/* Fields in TEGRA20_SPDIF_USR_DAT_TX_A */
+
+/*
+ * This 4-word deep FIFO transmits user FIFO field information. The order of
+ * transmission is from LSB to MSB bit.
+ */
+
+struct tegra20_spdif {
+ struct clk *clk_spdif_out;
+ 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_admaif.c b/sound/soc/tegra/tegra210_admaif.c
new file mode 100644
index 000000000..1a2e868a6
--- /dev/null
+++ b/sound/soc/tegra/tegra210_admaif.c
@@ -0,0 +1,872 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// tegra210_admaif.c - Tegra ADMAIF driver
+//
+// Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved.
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "tegra210_admaif.h"
+#include "tegra_cif.h"
+#include "tegra_pcm.h"
+
+#define CH_REG(offset, reg, id) \
+ ((offset) + (reg) + (TEGRA_ADMAIF_CHANNEL_REG_STRIDE * (id)))
+
+#define CH_TX_REG(reg, id) CH_REG(admaif->soc_data->tx_base, reg, id)
+
+#define CH_RX_REG(reg, id) CH_REG(admaif->soc_data->rx_base, reg, id)
+
+#define REG_DEFAULTS(id, rx_ctrl, tx_ctrl, tx_base, rx_base) \
+ { CH_REG(rx_base, TEGRA_ADMAIF_RX_INT_MASK, id), 0x00000001 }, \
+ { CH_REG(rx_base, TEGRA_ADMAIF_CH_ACIF_RX_CTRL, id), 0x00007700 }, \
+ { CH_REG(rx_base, TEGRA_ADMAIF_RX_FIFO_CTRL, id), rx_ctrl }, \
+ { CH_REG(tx_base, TEGRA_ADMAIF_TX_INT_MASK, id), 0x00000001 }, \
+ { CH_REG(tx_base, TEGRA_ADMAIF_CH_ACIF_TX_CTRL, id), 0x00007700 }, \
+ { CH_REG(tx_base, TEGRA_ADMAIF_TX_FIFO_CTRL, id), tx_ctrl }
+
+#define ADMAIF_REG_DEFAULTS(id, chip) \
+ REG_DEFAULTS((id) - 1, \
+ chip ## _ADMAIF_RX ## id ## _FIFO_CTRL_REG_DEFAULT, \
+ chip ## _ADMAIF_TX ## id ## _FIFO_CTRL_REG_DEFAULT, \
+ chip ## _ADMAIF_TX_BASE, \
+ chip ## _ADMAIF_RX_BASE)
+
+static const struct reg_default tegra186_admaif_reg_defaults[] = {
+ {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA186_ADMAIF_GLOBAL_BASE), 0x00000003},
+ ADMAIF_REG_DEFAULTS(1, TEGRA186),
+ ADMAIF_REG_DEFAULTS(2, TEGRA186),
+ ADMAIF_REG_DEFAULTS(3, TEGRA186),
+ ADMAIF_REG_DEFAULTS(4, TEGRA186),
+ ADMAIF_REG_DEFAULTS(5, TEGRA186),
+ ADMAIF_REG_DEFAULTS(6, TEGRA186),
+ ADMAIF_REG_DEFAULTS(7, TEGRA186),
+ ADMAIF_REG_DEFAULTS(8, TEGRA186),
+ ADMAIF_REG_DEFAULTS(9, TEGRA186),
+ ADMAIF_REG_DEFAULTS(10, TEGRA186),
+ ADMAIF_REG_DEFAULTS(11, TEGRA186),
+ ADMAIF_REG_DEFAULTS(12, TEGRA186),
+ ADMAIF_REG_DEFAULTS(13, TEGRA186),
+ ADMAIF_REG_DEFAULTS(14, TEGRA186),
+ ADMAIF_REG_DEFAULTS(15, TEGRA186),
+ ADMAIF_REG_DEFAULTS(16, TEGRA186),
+ ADMAIF_REG_DEFAULTS(17, TEGRA186),
+ ADMAIF_REG_DEFAULTS(18, TEGRA186),
+ ADMAIF_REG_DEFAULTS(19, TEGRA186),
+ ADMAIF_REG_DEFAULTS(20, TEGRA186)
+};
+
+static const struct reg_default tegra210_admaif_reg_defaults[] = {
+ {(TEGRA_ADMAIF_GLOBAL_CG_0 + TEGRA210_ADMAIF_GLOBAL_BASE), 0x00000003},
+ ADMAIF_REG_DEFAULTS(1, TEGRA210),
+ ADMAIF_REG_DEFAULTS(2, TEGRA210),
+ ADMAIF_REG_DEFAULTS(3, TEGRA210),
+ ADMAIF_REG_DEFAULTS(4, TEGRA210),
+ ADMAIF_REG_DEFAULTS(5, TEGRA210),
+ ADMAIF_REG_DEFAULTS(6, TEGRA210),
+ ADMAIF_REG_DEFAULTS(7, TEGRA210),
+ ADMAIF_REG_DEFAULTS(8, TEGRA210),
+ ADMAIF_REG_DEFAULTS(9, TEGRA210),
+ ADMAIF_REG_DEFAULTS(10, TEGRA210)
+};
+
+static bool tegra_admaif_wr_reg(struct device *dev, unsigned int reg)
+{
+ struct tegra_admaif *admaif = dev_get_drvdata(dev);
+ unsigned int ch_stride = TEGRA_ADMAIF_CHANNEL_REG_STRIDE;
+ unsigned int num_ch = admaif->soc_data->num_ch;
+ unsigned int rx_base = admaif->soc_data->rx_base;
+ unsigned int tx_base = admaif->soc_data->tx_base;
+ unsigned int global_base = admaif->soc_data->global_base;
+ unsigned int reg_max = admaif->soc_data->regmap_conf->max_register;
+ unsigned int rx_max = rx_base + (num_ch * ch_stride);
+ unsigned int tx_max = tx_base + (num_ch * ch_stride);
+
+ if ((reg >= rx_base) && (reg < rx_max)) {
+ reg = (reg - rx_base) % ch_stride;
+ if ((reg == TEGRA_ADMAIF_RX_ENABLE) ||
+ (reg == TEGRA_ADMAIF_RX_FIFO_CTRL) ||
+ (reg == TEGRA_ADMAIF_RX_SOFT_RESET) ||
+ (reg == TEGRA_ADMAIF_CH_ACIF_RX_CTRL))
+ return true;
+ } else if ((reg >= tx_base) && (reg < tx_max)) {
+ reg = (reg - tx_base) % ch_stride;
+ if ((reg == TEGRA_ADMAIF_TX_ENABLE) ||
+ (reg == TEGRA_ADMAIF_TX_FIFO_CTRL) ||
+ (reg == TEGRA_ADMAIF_TX_SOFT_RESET) ||
+ (reg == TEGRA_ADMAIF_CH_ACIF_TX_CTRL))
+ return true;
+ } else if ((reg >= global_base) && (reg < reg_max)) {
+ if (reg == (global_base + TEGRA_ADMAIF_GLOBAL_ENABLE))
+ return true;
+ }
+
+ return false;
+}
+
+static bool tegra_admaif_rd_reg(struct device *dev, unsigned int reg)
+{
+ struct tegra_admaif *admaif = dev_get_drvdata(dev);
+ unsigned int ch_stride = TEGRA_ADMAIF_CHANNEL_REG_STRIDE;
+ unsigned int num_ch = admaif->soc_data->num_ch;
+ unsigned int rx_base = admaif->soc_data->rx_base;
+ unsigned int tx_base = admaif->soc_data->tx_base;
+ unsigned int global_base = admaif->soc_data->global_base;
+ unsigned int reg_max = admaif->soc_data->regmap_conf->max_register;
+ unsigned int rx_max = rx_base + (num_ch * ch_stride);
+ unsigned int tx_max = tx_base + (num_ch * ch_stride);
+
+ if ((reg >= rx_base) && (reg < rx_max)) {
+ reg = (reg - rx_base) % ch_stride;
+ if ((reg == TEGRA_ADMAIF_RX_ENABLE) ||
+ (reg == TEGRA_ADMAIF_RX_STATUS) ||
+ (reg == TEGRA_ADMAIF_RX_INT_STATUS) ||
+ (reg == TEGRA_ADMAIF_RX_FIFO_CTRL) ||
+ (reg == TEGRA_ADMAIF_RX_SOFT_RESET) ||
+ (reg == TEGRA_ADMAIF_CH_ACIF_RX_CTRL))
+ return true;
+ } else if ((reg >= tx_base) && (reg < tx_max)) {
+ reg = (reg - tx_base) % ch_stride;
+ if ((reg == TEGRA_ADMAIF_TX_ENABLE) ||
+ (reg == TEGRA_ADMAIF_TX_STATUS) ||
+ (reg == TEGRA_ADMAIF_TX_INT_STATUS) ||
+ (reg == TEGRA_ADMAIF_TX_FIFO_CTRL) ||
+ (reg == TEGRA_ADMAIF_TX_SOFT_RESET) ||
+ (reg == TEGRA_ADMAIF_CH_ACIF_TX_CTRL))
+ return true;
+ } else if ((reg >= global_base) && (reg < reg_max)) {
+ if ((reg == (global_base + TEGRA_ADMAIF_GLOBAL_ENABLE)) ||
+ (reg == (global_base + TEGRA_ADMAIF_GLOBAL_CG_0)) ||
+ (reg == (global_base + TEGRA_ADMAIF_GLOBAL_STATUS)) ||
+ (reg == (global_base +
+ TEGRA_ADMAIF_GLOBAL_RX_ENABLE_STATUS)) ||
+ (reg == (global_base +
+ TEGRA_ADMAIF_GLOBAL_TX_ENABLE_STATUS)))
+ return true;
+ }
+
+ return false;
+}
+
+static bool tegra_admaif_volatile_reg(struct device *dev, unsigned int reg)
+{
+ struct tegra_admaif *admaif = dev_get_drvdata(dev);
+ unsigned int ch_stride = TEGRA_ADMAIF_CHANNEL_REG_STRIDE;
+ unsigned int num_ch = admaif->soc_data->num_ch;
+ unsigned int rx_base = admaif->soc_data->rx_base;
+ unsigned int tx_base = admaif->soc_data->tx_base;
+ unsigned int global_base = admaif->soc_data->global_base;
+ unsigned int reg_max = admaif->soc_data->regmap_conf->max_register;
+ unsigned int rx_max = rx_base + (num_ch * ch_stride);
+ unsigned int tx_max = tx_base + (num_ch * ch_stride);
+
+ if ((reg >= rx_base) && (reg < rx_max)) {
+ reg = (reg - rx_base) % ch_stride;
+ if ((reg == TEGRA_ADMAIF_RX_ENABLE) ||
+ (reg == TEGRA_ADMAIF_RX_STATUS) ||
+ (reg == TEGRA_ADMAIF_RX_INT_STATUS) ||
+ (reg == TEGRA_ADMAIF_RX_SOFT_RESET))
+ return true;
+ } else if ((reg >= tx_base) && (reg < tx_max)) {
+ reg = (reg - tx_base) % ch_stride;
+ if ((reg == TEGRA_ADMAIF_TX_ENABLE) ||
+ (reg == TEGRA_ADMAIF_TX_STATUS) ||
+ (reg == TEGRA_ADMAIF_TX_INT_STATUS) ||
+ (reg == TEGRA_ADMAIF_TX_SOFT_RESET))
+ return true;
+ } else if ((reg >= global_base) && (reg < reg_max)) {
+ if ((reg == (global_base + TEGRA_ADMAIF_GLOBAL_STATUS)) ||
+ (reg == (global_base +
+ TEGRA_ADMAIF_GLOBAL_RX_ENABLE_STATUS)) ||
+ (reg == (global_base +
+ TEGRA_ADMAIF_GLOBAL_TX_ENABLE_STATUS)))
+ return true;
+ }
+
+ return false;
+}
+
+static const struct regmap_config tegra210_admaif_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA210_ADMAIF_LAST_REG,
+ .writeable_reg = tegra_admaif_wr_reg,
+ .readable_reg = tegra_admaif_rd_reg,
+ .volatile_reg = tegra_admaif_volatile_reg,
+ .reg_defaults = tegra210_admaif_reg_defaults,
+ .num_reg_defaults = TEGRA210_ADMAIF_CHANNEL_COUNT * 6 + 1,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct regmap_config tegra186_admaif_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA186_ADMAIF_LAST_REG,
+ .writeable_reg = tegra_admaif_wr_reg,
+ .readable_reg = tegra_admaif_rd_reg,
+ .volatile_reg = tegra_admaif_volatile_reg,
+ .reg_defaults = tegra186_admaif_reg_defaults,
+ .num_reg_defaults = TEGRA186_ADMAIF_CHANNEL_COUNT * 6 + 1,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int __maybe_unused tegra_admaif_runtime_suspend(struct device *dev)
+{
+ struct tegra_admaif *admaif = dev_get_drvdata(dev);
+
+ regcache_cache_only(admaif->regmap, true);
+ regcache_mark_dirty(admaif->regmap);
+
+ return 0;
+}
+
+static int __maybe_unused tegra_admaif_runtime_resume(struct device *dev)
+{
+ struct tegra_admaif *admaif = dev_get_drvdata(dev);
+
+ regcache_cache_only(admaif->regmap, false);
+ regcache_sync(admaif->regmap);
+
+ return 0;
+}
+
+static int tegra_admaif_set_pack_mode(struct regmap *map, unsigned int reg,
+ int valid_bit)
+{
+ switch (valid_bit) {
+ case DATA_8BIT:
+ regmap_update_bits(map, reg, PACK8_EN_MASK, PACK8_EN);
+ regmap_update_bits(map, reg, PACK16_EN_MASK, 0);
+ break;
+ case DATA_16BIT:
+ regmap_update_bits(map, reg, PACK16_EN_MASK, PACK16_EN);
+ regmap_update_bits(map, reg, PACK8_EN_MASK, 0);
+ break;
+ case DATA_32BIT:
+ regmap_update_bits(map, reg, PACK16_EN_MASK, 0);
+ regmap_update_bits(map, reg, PACK8_EN_MASK, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tegra_admaif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct device *dev = dai->dev;
+ struct tegra_admaif *admaif = snd_soc_dai_get_drvdata(dai);
+ struct tegra_cif_conf cif_conf;
+ unsigned int reg, path;
+ int valid_bit, channels;
+
+ memset(&cif_conf, 0, sizeof(struct tegra_cif_conf));
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ cif_conf.audio_bits = TEGRA_ACIF_BITS_8;
+ cif_conf.client_bits = TEGRA_ACIF_BITS_8;
+ valid_bit = DATA_8BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ cif_conf.audio_bits = TEGRA_ACIF_BITS_16;
+ cif_conf.client_bits = TEGRA_ACIF_BITS_16;
+ valid_bit = DATA_16BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ cif_conf.audio_bits = TEGRA_ACIF_BITS_32;
+ cif_conf.client_bits = TEGRA_ACIF_BITS_32;
+ valid_bit = DATA_32BIT;
+ break;
+ default:
+ dev_err(dev, "unsupported format!\n");
+ return -EOPNOTSUPP;
+ }
+
+ channels = params_channels(params);
+ cif_conf.client_ch = channels;
+ cif_conf.audio_ch = channels;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ path = ADMAIF_TX_PATH;
+ reg = CH_TX_REG(TEGRA_ADMAIF_CH_ACIF_TX_CTRL, dai->id);
+ } else {
+ path = ADMAIF_RX_PATH;
+ reg = CH_RX_REG(TEGRA_ADMAIF_CH_ACIF_RX_CTRL, dai->id);
+ }
+
+ cif_conf.mono_conv = admaif->mono_to_stereo[path][dai->id];
+ cif_conf.stereo_conv = admaif->stereo_to_mono[path][dai->id];
+
+ tegra_admaif_set_pack_mode(admaif->regmap, reg, valid_bit);
+
+ tegra_set_cif(admaif->regmap, reg, &cif_conf);
+
+ return 0;
+}
+
+static int tegra_admaif_start(struct snd_soc_dai *dai, int direction)
+{
+ struct tegra_admaif *admaif = snd_soc_dai_get_drvdata(dai);
+ unsigned int reg, mask, val;
+
+ switch (direction) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ mask = TX_ENABLE_MASK;
+ val = TX_ENABLE;
+ reg = CH_TX_REG(TEGRA_ADMAIF_TX_ENABLE, dai->id);
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ mask = RX_ENABLE_MASK;
+ val = RX_ENABLE;
+ reg = CH_RX_REG(TEGRA_ADMAIF_RX_ENABLE, dai->id);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(admaif->regmap, reg, mask, val);
+
+ return 0;
+}
+
+static int tegra_admaif_stop(struct snd_soc_dai *dai, int direction)
+{
+ struct tegra_admaif *admaif = snd_soc_dai_get_drvdata(dai);
+ unsigned int enable_reg, status_reg, reset_reg, mask, val;
+ char *dir_name;
+ int err, enable;
+
+ switch (direction) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ mask = TX_ENABLE_MASK;
+ enable = TX_ENABLE;
+ dir_name = "TX";
+ enable_reg = CH_TX_REG(TEGRA_ADMAIF_TX_ENABLE, dai->id);
+ status_reg = CH_TX_REG(TEGRA_ADMAIF_TX_STATUS, dai->id);
+ reset_reg = CH_TX_REG(TEGRA_ADMAIF_TX_SOFT_RESET, dai->id);
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ mask = RX_ENABLE_MASK;
+ enable = RX_ENABLE;
+ dir_name = "RX";
+ enable_reg = CH_RX_REG(TEGRA_ADMAIF_RX_ENABLE, dai->id);
+ status_reg = CH_RX_REG(TEGRA_ADMAIF_RX_STATUS, dai->id);
+ reset_reg = CH_RX_REG(TEGRA_ADMAIF_RX_SOFT_RESET, dai->id);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Disable TX/RX channel */
+ regmap_update_bits(admaif->regmap, enable_reg, mask, ~enable);
+
+ /* Wait until ADMAIF TX/RX status is disabled */
+ err = regmap_read_poll_timeout_atomic(admaif->regmap, status_reg, val,
+ !(val & enable), 10, 10000);
+ if (err < 0)
+ dev_warn(dai->dev, "timeout: failed to disable ADMAIF%d_%s\n",
+ dai->id + 1, dir_name);
+
+ /* SW reset */
+ regmap_update_bits(admaif->regmap, reset_reg, SW_RESET_MASK, SW_RESET);
+
+ /* Wait till SW reset is complete */
+ err = regmap_read_poll_timeout_atomic(admaif->regmap, reset_reg, val,
+ !(val & SW_RESET_MASK & SW_RESET),
+ 10, 10000);
+ if (err) {
+ dev_err(dai->dev, "timeout: SW reset failed for ADMAIF%d_%s\n",
+ dai->id + 1, dir_name);
+ return err;
+ }
+
+ return 0;
+}
+
+static int tegra_admaif_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int err;
+
+ err = snd_dmaengine_pcm_trigger(substream, cmd);
+ if (err)
+ return err;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ return tegra_admaif_start(dai, substream->stream);
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ return tegra_admaif_stop(dai, substream->stream);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct snd_soc_dai_ops tegra_admaif_dai_ops = {
+ .hw_params = tegra_admaif_hw_params,
+ .trigger = tegra_admaif_trigger,
+};
+
+static int tegra210_admaif_pget_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt);
+ struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value;
+
+ ucontrol->value.enumerated.item[0] =
+ admaif->mono_to_stereo[ADMAIF_TX_PATH][ec->reg];
+
+ return 0;
+}
+
+static int tegra210_admaif_pput_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt);
+ struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value;
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == admaif->mono_to_stereo[ADMAIF_TX_PATH][ec->reg])
+ return 0;
+
+ admaif->mono_to_stereo[ADMAIF_TX_PATH][ec->reg] = value;
+
+ return 1;
+}
+
+static int tegra210_admaif_cget_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt);
+ struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value;
+
+ ucontrol->value.enumerated.item[0] =
+ admaif->mono_to_stereo[ADMAIF_RX_PATH][ec->reg];
+
+ return 0;
+}
+
+static int tegra210_admaif_cput_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt);
+ struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value;
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == admaif->mono_to_stereo[ADMAIF_RX_PATH][ec->reg])
+ return 0;
+
+ admaif->mono_to_stereo[ADMAIF_RX_PATH][ec->reg] = value;
+
+ return 1;
+}
+
+static int tegra210_admaif_pget_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt);
+ struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value;
+
+ ucontrol->value.enumerated.item[0] =
+ admaif->stereo_to_mono[ADMAIF_TX_PATH][ec->reg];
+
+ return 0;
+}
+
+static int tegra210_admaif_pput_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt);
+ struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value;
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == admaif->stereo_to_mono[ADMAIF_TX_PATH][ec->reg])
+ return 0;
+
+ admaif->stereo_to_mono[ADMAIF_TX_PATH][ec->reg] = value;
+
+ return 1;
+}
+
+static int tegra210_admaif_cget_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt);
+ struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value;
+
+ ucontrol->value.enumerated.item[0] =
+ admaif->stereo_to_mono[ADMAIF_RX_PATH][ec->reg];
+
+ return 0;
+}
+
+static int tegra210_admaif_cput_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra_admaif *admaif = snd_soc_component_get_drvdata(cmpnt);
+ struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value;
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == admaif->stereo_to_mono[ADMAIF_RX_PATH][ec->reg])
+ return 0;
+
+ admaif->stereo_to_mono[ADMAIF_RX_PATH][ec->reg] = value;
+
+ return 1;
+}
+
+static int tegra_admaif_dai_probe(struct snd_soc_dai *dai)
+{
+ struct tegra_admaif *admaif = snd_soc_dai_get_drvdata(dai);
+
+ dai->capture_dma_data = &admaif->capture_dma_data[dai->id];
+ dai->playback_dma_data = &admaif->playback_dma_data[dai->id];
+
+ return 0;
+}
+
+#define DAI(dai_name) \
+ { \
+ .name = dai_name, \
+ .probe = tegra_admaif_dai_probe, \
+ .playback = { \
+ .stream_name = dai_name " Playback", \
+ .channels_min = 1, \
+ .channels_max = 16, \
+ .rates = SNDRV_PCM_RATE_8000_192000, \
+ .formats = SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ }, \
+ .capture = { \
+ .stream_name = dai_name " Capture", \
+ .channels_min = 1, \
+ .channels_max = 16, \
+ .rates = SNDRV_PCM_RATE_8000_192000, \
+ .formats = SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ }, \
+ .ops = &tegra_admaif_dai_ops, \
+ }
+
+static struct snd_soc_dai_driver tegra210_admaif_cmpnt_dais[] = {
+ DAI("ADMAIF1"),
+ DAI("ADMAIF2"),
+ DAI("ADMAIF3"),
+ DAI("ADMAIF4"),
+ DAI("ADMAIF5"),
+ DAI("ADMAIF6"),
+ DAI("ADMAIF7"),
+ DAI("ADMAIF8"),
+ DAI("ADMAIF9"),
+ DAI("ADMAIF10"),
+};
+
+static struct snd_soc_dai_driver tegra186_admaif_cmpnt_dais[] = {
+ DAI("ADMAIF1"),
+ DAI("ADMAIF2"),
+ DAI("ADMAIF3"),
+ DAI("ADMAIF4"),
+ DAI("ADMAIF5"),
+ DAI("ADMAIF6"),
+ DAI("ADMAIF7"),
+ DAI("ADMAIF8"),
+ DAI("ADMAIF9"),
+ DAI("ADMAIF10"),
+ DAI("ADMAIF11"),
+ DAI("ADMAIF12"),
+ DAI("ADMAIF13"),
+ DAI("ADMAIF14"),
+ DAI("ADMAIF15"),
+ DAI("ADMAIF16"),
+ DAI("ADMAIF17"),
+ DAI("ADMAIF18"),
+ DAI("ADMAIF19"),
+ DAI("ADMAIF20"),
+};
+
+static const char * const tegra_admaif_stereo_conv_text[] = {
+ "CH0", "CH1", "AVG",
+};
+
+static const char * const tegra_admaif_mono_conv_text[] = {
+ "Zero", "Copy",
+};
+
+/*
+ * Below macro is added to avoid looping over all ADMAIFx controls related
+ * to mono/stereo conversions in get()/put() callbacks.
+ */
+#define NV_SOC_ENUM_EXT(xname, xreg, xhandler_get, xhandler_put, xenum_text) \
+{ \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .info = snd_soc_info_enum_double, \
+ .name = xname, \
+ .get = xhandler_get, \
+ .put = xhandler_put, \
+ .private_value = (unsigned long)&(struct soc_enum) \
+ SOC_ENUM_SINGLE(xreg, 0, ARRAY_SIZE(xenum_text), xenum_text) \
+}
+
+#define TEGRA_ADMAIF_CIF_CTRL(reg) \
+ NV_SOC_ENUM_EXT("ADMAIF" #reg " Playback Mono To Stereo", reg - 1, \
+ tegra210_admaif_pget_mono_to_stereo, \
+ tegra210_admaif_pput_mono_to_stereo, \
+ tegra_admaif_mono_conv_text), \
+ NV_SOC_ENUM_EXT("ADMAIF" #reg " Playback Stereo To Mono", reg - 1, \
+ tegra210_admaif_pget_stereo_to_mono, \
+ tegra210_admaif_pput_stereo_to_mono, \
+ tegra_admaif_stereo_conv_text), \
+ NV_SOC_ENUM_EXT("ADMAIF" #reg " Capture Mono To Stereo", reg - 1, \
+ tegra210_admaif_cget_mono_to_stereo, \
+ tegra210_admaif_cput_mono_to_stereo, \
+ tegra_admaif_mono_conv_text), \
+ NV_SOC_ENUM_EXT("ADMAIF" #reg " Capture Stereo To Mono", reg - 1, \
+ tegra210_admaif_cget_stereo_to_mono, \
+ tegra210_admaif_cput_stereo_to_mono, \
+ tegra_admaif_stereo_conv_text)
+
+static struct snd_kcontrol_new tegra210_admaif_controls[] = {
+ TEGRA_ADMAIF_CIF_CTRL(1),
+ TEGRA_ADMAIF_CIF_CTRL(2),
+ TEGRA_ADMAIF_CIF_CTRL(3),
+ TEGRA_ADMAIF_CIF_CTRL(4),
+ TEGRA_ADMAIF_CIF_CTRL(5),
+ TEGRA_ADMAIF_CIF_CTRL(6),
+ TEGRA_ADMAIF_CIF_CTRL(7),
+ TEGRA_ADMAIF_CIF_CTRL(8),
+ TEGRA_ADMAIF_CIF_CTRL(9),
+ TEGRA_ADMAIF_CIF_CTRL(10),
+};
+
+static struct snd_kcontrol_new tegra186_admaif_controls[] = {
+ TEGRA_ADMAIF_CIF_CTRL(1),
+ TEGRA_ADMAIF_CIF_CTRL(2),
+ TEGRA_ADMAIF_CIF_CTRL(3),
+ TEGRA_ADMAIF_CIF_CTRL(4),
+ TEGRA_ADMAIF_CIF_CTRL(5),
+ TEGRA_ADMAIF_CIF_CTRL(6),
+ TEGRA_ADMAIF_CIF_CTRL(7),
+ TEGRA_ADMAIF_CIF_CTRL(8),
+ TEGRA_ADMAIF_CIF_CTRL(9),
+ TEGRA_ADMAIF_CIF_CTRL(10),
+ TEGRA_ADMAIF_CIF_CTRL(11),
+ TEGRA_ADMAIF_CIF_CTRL(12),
+ TEGRA_ADMAIF_CIF_CTRL(13),
+ TEGRA_ADMAIF_CIF_CTRL(14),
+ TEGRA_ADMAIF_CIF_CTRL(15),
+ TEGRA_ADMAIF_CIF_CTRL(16),
+ TEGRA_ADMAIF_CIF_CTRL(17),
+ TEGRA_ADMAIF_CIF_CTRL(18),
+ TEGRA_ADMAIF_CIF_CTRL(19),
+ TEGRA_ADMAIF_CIF_CTRL(20),
+};
+
+static const struct snd_soc_component_driver tegra210_admaif_cmpnt = {
+ .controls = tegra210_admaif_controls,
+ .num_controls = ARRAY_SIZE(tegra210_admaif_controls),
+ .pcm_construct = tegra_pcm_construct,
+ .open = tegra_pcm_open,
+ .close = tegra_pcm_close,
+ .hw_params = tegra_pcm_hw_params,
+ .pointer = tegra_pcm_pointer,
+};
+
+static const struct snd_soc_component_driver tegra186_admaif_cmpnt = {
+ .controls = tegra186_admaif_controls,
+ .num_controls = ARRAY_SIZE(tegra186_admaif_controls),
+ .pcm_construct = tegra_pcm_construct,
+ .open = tegra_pcm_open,
+ .close = tegra_pcm_close,
+ .hw_params = tegra_pcm_hw_params,
+ .pointer = tegra_pcm_pointer,
+};
+
+static const struct tegra_admaif_soc_data soc_data_tegra210 = {
+ .num_ch = TEGRA210_ADMAIF_CHANNEL_COUNT,
+ .cmpnt = &tegra210_admaif_cmpnt,
+ .dais = tegra210_admaif_cmpnt_dais,
+ .regmap_conf = &tegra210_admaif_regmap_config,
+ .global_base = TEGRA210_ADMAIF_GLOBAL_BASE,
+ .tx_base = TEGRA210_ADMAIF_TX_BASE,
+ .rx_base = TEGRA210_ADMAIF_RX_BASE,
+};
+
+static const struct tegra_admaif_soc_data soc_data_tegra186 = {
+ .num_ch = TEGRA186_ADMAIF_CHANNEL_COUNT,
+ .cmpnt = &tegra186_admaif_cmpnt,
+ .dais = tegra186_admaif_cmpnt_dais,
+ .regmap_conf = &tegra186_admaif_regmap_config,
+ .global_base = TEGRA186_ADMAIF_GLOBAL_BASE,
+ .tx_base = TEGRA186_ADMAIF_TX_BASE,
+ .rx_base = TEGRA186_ADMAIF_RX_BASE,
+};
+
+static const struct of_device_id tegra_admaif_of_match[] = {
+ { .compatible = "nvidia,tegra210-admaif", .data = &soc_data_tegra210 },
+ { .compatible = "nvidia,tegra186-admaif", .data = &soc_data_tegra186 },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra_admaif_of_match);
+
+static int tegra_admaif_probe(struct platform_device *pdev)
+{
+ struct tegra_admaif *admaif;
+ void __iomem *regs;
+ struct resource *res;
+ int err, i;
+
+ admaif = devm_kzalloc(&pdev->dev, sizeof(*admaif), GFP_KERNEL);
+ if (!admaif)
+ return -ENOMEM;
+
+ admaif->soc_data = of_device_get_match_data(&pdev->dev);
+
+ dev_set_drvdata(&pdev->dev, admaif);
+
+ admaif->capture_dma_data =
+ devm_kcalloc(&pdev->dev,
+ admaif->soc_data->num_ch,
+ sizeof(struct snd_dmaengine_dai_dma_data),
+ GFP_KERNEL);
+ if (!admaif->capture_dma_data)
+ return -ENOMEM;
+
+ admaif->playback_dma_data =
+ devm_kcalloc(&pdev->dev,
+ admaif->soc_data->num_ch,
+ sizeof(struct snd_dmaengine_dai_dma_data),
+ GFP_KERNEL);
+ if (!admaif->playback_dma_data)
+ return -ENOMEM;
+
+ for (i = 0; i < ADMAIF_PATHS; i++) {
+ admaif->mono_to_stereo[i] =
+ devm_kcalloc(&pdev->dev, admaif->soc_data->num_ch,
+ sizeof(unsigned int), GFP_KERNEL);
+ if (!admaif->mono_to_stereo[i])
+ return -ENOMEM;
+
+ admaif->stereo_to_mono[i] =
+ devm_kcalloc(&pdev->dev, admaif->soc_data->num_ch,
+ sizeof(unsigned int), GFP_KERNEL);
+ if (!admaif->stereo_to_mono[i])
+ return -ENOMEM;
+ }
+
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ admaif->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
+ admaif->soc_data->regmap_conf);
+ if (IS_ERR(admaif->regmap)) {
+ dev_err(&pdev->dev, "regmap init failed\n");
+ return PTR_ERR(admaif->regmap);
+ }
+
+ regcache_cache_only(admaif->regmap, true);
+
+ regmap_update_bits(admaif->regmap, admaif->soc_data->global_base +
+ TEGRA_ADMAIF_GLOBAL_ENABLE, 1, 1);
+
+ for (i = 0; i < admaif->soc_data->num_ch; i++) {
+ admaif->playback_dma_data[i].addr = res->start +
+ CH_TX_REG(TEGRA_ADMAIF_TX_FIFO_WRITE, i);
+
+ admaif->capture_dma_data[i].addr = res->start +
+ CH_RX_REG(TEGRA_ADMAIF_RX_FIFO_READ, i);
+
+ admaif->playback_dma_data[i].addr_width = 32;
+
+ if (of_property_read_string_index(pdev->dev.of_node,
+ "dma-names", (i * 2) + 1,
+ &admaif->playback_dma_data[i].chan_name) < 0) {
+ dev_err(&pdev->dev,
+ "missing property nvidia,dma-names\n");
+
+ return -ENODEV;
+ }
+
+ admaif->capture_dma_data[i].addr_width = 32;
+
+ if (of_property_read_string_index(pdev->dev.of_node,
+ "dma-names",
+ (i * 2),
+ &admaif->capture_dma_data[i].chan_name) < 0) {
+ dev_err(&pdev->dev,
+ "missing property nvidia,dma-names\n");
+
+ return -ENODEV;
+ }
+ }
+
+ err = devm_snd_soc_register_component(&pdev->dev,
+ admaif->soc_data->cmpnt,
+ admaif->soc_data->dais,
+ admaif->soc_data->num_ch);
+ if (err) {
+ dev_err(&pdev->dev,
+ "can't register ADMAIF component, err: %d\n", err);
+ return err;
+ }
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+}
+
+static int tegra_admaif_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra_admaif_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra_admaif_runtime_suspend,
+ tegra_admaif_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver tegra_admaif_driver = {
+ .probe = tegra_admaif_probe,
+ .remove = tegra_admaif_remove,
+ .driver = {
+ .name = "tegra210-admaif",
+ .of_match_table = tegra_admaif_of_match,
+ .pm = &tegra_admaif_pm_ops,
+ },
+};
+module_platform_driver(tegra_admaif_driver);
+
+MODULE_AUTHOR("Songhee Baek <sbaek@nvidia.com>");
+MODULE_DESCRIPTION("Tegra210 ASoC ADMAIF driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/tegra/tegra210_admaif.h b/sound/soc/tegra/tegra210_admaif.h
new file mode 100644
index 000000000..96686dc92
--- /dev/null
+++ b/sound/soc/tegra/tegra210_admaif.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra210_admaif.h - Tegra ADMAIF registers
+ *
+ * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved.
+ *
+ */
+
+#ifndef __TEGRA_ADMAIF_H__
+#define __TEGRA_ADMAIF_H__
+
+#define TEGRA_ADMAIF_CHANNEL_REG_STRIDE 0x40
+/* Tegra210 specific */
+#define TEGRA210_ADMAIF_LAST_REG 0x75f
+#define TEGRA210_ADMAIF_CHANNEL_COUNT 10
+#define TEGRA210_ADMAIF_RX_BASE 0x0
+#define TEGRA210_ADMAIF_TX_BASE 0x300
+#define TEGRA210_ADMAIF_GLOBAL_BASE 0x700
+/* Tegra186 specific */
+#define TEGRA186_ADMAIF_LAST_REG 0xd5f
+#define TEGRA186_ADMAIF_CHANNEL_COUNT 20
+#define TEGRA186_ADMAIF_RX_BASE 0x0
+#define TEGRA186_ADMAIF_TX_BASE 0x500
+#define TEGRA186_ADMAIF_GLOBAL_BASE 0xd00
+/* Global registers */
+#define TEGRA_ADMAIF_GLOBAL_ENABLE 0x0
+#define TEGRA_ADMAIF_GLOBAL_CG_0 0x8
+#define TEGRA_ADMAIF_GLOBAL_STATUS 0x10
+#define TEGRA_ADMAIF_GLOBAL_RX_ENABLE_STATUS 0x20
+#define TEGRA_ADMAIF_GLOBAL_TX_ENABLE_STATUS 0x24
+/* RX channel registers */
+#define TEGRA_ADMAIF_RX_ENABLE 0x0
+#define TEGRA_ADMAIF_RX_SOFT_RESET 0x4
+#define TEGRA_ADMAIF_RX_STATUS 0xc
+#define TEGRA_ADMAIF_RX_INT_STATUS 0x10
+#define TEGRA_ADMAIF_RX_INT_MASK 0x14
+#define TEGRA_ADMAIF_RX_INT_SET 0x18
+#define TEGRA_ADMAIF_RX_INT_CLEAR 0x1c
+#define TEGRA_ADMAIF_CH_ACIF_RX_CTRL 0x20
+#define TEGRA_ADMAIF_RX_FIFO_CTRL 0x28
+#define TEGRA_ADMAIF_RX_FIFO_READ 0x2c
+/* TX channel registers */
+#define TEGRA_ADMAIF_TX_ENABLE 0x0
+#define TEGRA_ADMAIF_TX_SOFT_RESET 0x4
+#define TEGRA_ADMAIF_TX_STATUS 0xc
+#define TEGRA_ADMAIF_TX_INT_STATUS 0x10
+#define TEGRA_ADMAIF_TX_INT_MASK 0x14
+#define TEGRA_ADMAIF_TX_INT_SET 0x18
+#define TEGRA_ADMAIF_TX_INT_CLEAR 0x1c
+#define TEGRA_ADMAIF_CH_ACIF_TX_CTRL 0x20
+#define TEGRA_ADMAIF_TX_FIFO_CTRL 0x28
+#define TEGRA_ADMAIF_TX_FIFO_WRITE 0x2c
+/* Bit fields */
+#define PACK8_EN_SHIFT 31
+#define PACK8_EN_MASK BIT(PACK8_EN_SHIFT)
+#define PACK8_EN BIT(PACK8_EN_SHIFT)
+#define PACK16_EN_SHIFT 30
+#define PACK16_EN_MASK BIT(PACK16_EN_SHIFT)
+#define PACK16_EN BIT(PACK16_EN_SHIFT)
+#define TX_ENABLE_SHIFT 0
+#define TX_ENABLE_MASK BIT(TX_ENABLE_SHIFT)
+#define TX_ENABLE BIT(TX_ENABLE_SHIFT)
+#define RX_ENABLE_SHIFT 0
+#define RX_ENABLE_MASK BIT(RX_ENABLE_SHIFT)
+#define RX_ENABLE BIT(RX_ENABLE_SHIFT)
+#define SW_RESET_MASK 1
+#define SW_RESET 1
+/* Default values - Tegra210 */
+#define TEGRA210_ADMAIF_RX1_FIFO_CTRL_REG_DEFAULT 0x00000300
+#define TEGRA210_ADMAIF_RX2_FIFO_CTRL_REG_DEFAULT 0x00000304
+#define TEGRA210_ADMAIF_RX3_FIFO_CTRL_REG_DEFAULT 0x00000208
+#define TEGRA210_ADMAIF_RX4_FIFO_CTRL_REG_DEFAULT 0x0000020b
+#define TEGRA210_ADMAIF_RX5_FIFO_CTRL_REG_DEFAULT 0x0000020e
+#define TEGRA210_ADMAIF_RX6_FIFO_CTRL_REG_DEFAULT 0x00000211
+#define TEGRA210_ADMAIF_RX7_FIFO_CTRL_REG_DEFAULT 0x00000214
+#define TEGRA210_ADMAIF_RX8_FIFO_CTRL_REG_DEFAULT 0x00000217
+#define TEGRA210_ADMAIF_RX9_FIFO_CTRL_REG_DEFAULT 0x0000021a
+#define TEGRA210_ADMAIF_RX10_FIFO_CTRL_REG_DEFAULT 0x0000021d
+#define TEGRA210_ADMAIF_TX1_FIFO_CTRL_REG_DEFAULT 0x02000300
+#define TEGRA210_ADMAIF_TX2_FIFO_CTRL_REG_DEFAULT 0x02000304
+#define TEGRA210_ADMAIF_TX3_FIFO_CTRL_REG_DEFAULT 0x01800208
+#define TEGRA210_ADMAIF_TX4_FIFO_CTRL_REG_DEFAULT 0x0180020b
+#define TEGRA210_ADMAIF_TX5_FIFO_CTRL_REG_DEFAULT 0x0180020e
+#define TEGRA210_ADMAIF_TX6_FIFO_CTRL_REG_DEFAULT 0x01800211
+#define TEGRA210_ADMAIF_TX7_FIFO_CTRL_REG_DEFAULT 0x01800214
+#define TEGRA210_ADMAIF_TX8_FIFO_CTRL_REG_DEFAULT 0x01800217
+#define TEGRA210_ADMAIF_TX9_FIFO_CTRL_REG_DEFAULT 0x0180021a
+#define TEGRA210_ADMAIF_TX10_FIFO_CTRL_REG_DEFAULT 0x0180021d
+/* Default values - Tegra186 */
+#define TEGRA186_ADMAIF_RX1_FIFO_CTRL_REG_DEFAULT 0x00000300
+#define TEGRA186_ADMAIF_RX2_FIFO_CTRL_REG_DEFAULT 0x00000304
+#define TEGRA186_ADMAIF_RX3_FIFO_CTRL_REG_DEFAULT 0x00000308
+#define TEGRA186_ADMAIF_RX4_FIFO_CTRL_REG_DEFAULT 0x0000030c
+#define TEGRA186_ADMAIF_RX5_FIFO_CTRL_REG_DEFAULT 0x00000210
+#define TEGRA186_ADMAIF_RX6_FIFO_CTRL_REG_DEFAULT 0x00000213
+#define TEGRA186_ADMAIF_RX7_FIFO_CTRL_REG_DEFAULT 0x00000216
+#define TEGRA186_ADMAIF_RX8_FIFO_CTRL_REG_DEFAULT 0x00000219
+#define TEGRA186_ADMAIF_RX9_FIFO_CTRL_REG_DEFAULT 0x0000021c
+#define TEGRA186_ADMAIF_RX10_FIFO_CTRL_REG_DEFAULT 0x0000021f
+#define TEGRA186_ADMAIF_RX11_FIFO_CTRL_REG_DEFAULT 0x00000222
+#define TEGRA186_ADMAIF_RX12_FIFO_CTRL_REG_DEFAULT 0x00000225
+#define TEGRA186_ADMAIF_RX13_FIFO_CTRL_REG_DEFAULT 0x00000228
+#define TEGRA186_ADMAIF_RX14_FIFO_CTRL_REG_DEFAULT 0x0000022b
+#define TEGRA186_ADMAIF_RX15_FIFO_CTRL_REG_DEFAULT 0x0000022e
+#define TEGRA186_ADMAIF_RX16_FIFO_CTRL_REG_DEFAULT 0x00000231
+#define TEGRA186_ADMAIF_RX17_FIFO_CTRL_REG_DEFAULT 0x00000234
+#define TEGRA186_ADMAIF_RX18_FIFO_CTRL_REG_DEFAULT 0x00000237
+#define TEGRA186_ADMAIF_RX19_FIFO_CTRL_REG_DEFAULT 0x0000023a
+#define TEGRA186_ADMAIF_RX20_FIFO_CTRL_REG_DEFAULT 0x0000023d
+#define TEGRA186_ADMAIF_TX1_FIFO_CTRL_REG_DEFAULT 0x02000300
+#define TEGRA186_ADMAIF_TX2_FIFO_CTRL_REG_DEFAULT 0x02000304
+#define TEGRA186_ADMAIF_TX3_FIFO_CTRL_REG_DEFAULT 0x02000308
+#define TEGRA186_ADMAIF_TX4_FIFO_CTRL_REG_DEFAULT 0x0200030c
+#define TEGRA186_ADMAIF_TX5_FIFO_CTRL_REG_DEFAULT 0x01800210
+#define TEGRA186_ADMAIF_TX6_FIFO_CTRL_REG_DEFAULT 0x01800213
+#define TEGRA186_ADMAIF_TX7_FIFO_CTRL_REG_DEFAULT 0x01800216
+#define TEGRA186_ADMAIF_TX8_FIFO_CTRL_REG_DEFAULT 0x01800219
+#define TEGRA186_ADMAIF_TX9_FIFO_CTRL_REG_DEFAULT 0x0180021c
+#define TEGRA186_ADMAIF_TX10_FIFO_CTRL_REG_DEFAULT 0x0180021f
+#define TEGRA186_ADMAIF_TX11_FIFO_CTRL_REG_DEFAULT 0x01800222
+#define TEGRA186_ADMAIF_TX12_FIFO_CTRL_REG_DEFAULT 0x01800225
+#define TEGRA186_ADMAIF_TX13_FIFO_CTRL_REG_DEFAULT 0x01800228
+#define TEGRA186_ADMAIF_TX14_FIFO_CTRL_REG_DEFAULT 0x0180022b
+#define TEGRA186_ADMAIF_TX15_FIFO_CTRL_REG_DEFAULT 0x0180022e
+#define TEGRA186_ADMAIF_TX16_FIFO_CTRL_REG_DEFAULT 0x01800231
+#define TEGRA186_ADMAIF_TX17_FIFO_CTRL_REG_DEFAULT 0x01800234
+#define TEGRA186_ADMAIF_TX18_FIFO_CTRL_REG_DEFAULT 0x01800237
+#define TEGRA186_ADMAIF_TX19_FIFO_CTRL_REG_DEFAULT 0x0180023a
+#define TEGRA186_ADMAIF_TX20_FIFO_CTRL_REG_DEFAULT 0x0180023d
+
+enum {
+ DATA_8BIT,
+ DATA_16BIT,
+ DATA_32BIT
+};
+
+enum {
+ ADMAIF_RX_PATH,
+ ADMAIF_TX_PATH,
+ ADMAIF_PATHS,
+};
+
+struct tegra_admaif_soc_data {
+ const struct snd_soc_component_driver *cmpnt;
+ const struct regmap_config *regmap_conf;
+ struct snd_soc_dai_driver *dais;
+ unsigned int global_base;
+ unsigned int tx_base;
+ unsigned int rx_base;
+ unsigned int num_ch;
+};
+
+struct tegra_admaif {
+ struct snd_dmaengine_dai_dma_data *capture_dma_data;
+ struct snd_dmaengine_dai_dma_data *playback_dma_data;
+ const struct tegra_admaif_soc_data *soc_data;
+ unsigned int *mono_to_stereo[ADMAIF_PATHS];
+ unsigned int *stereo_to_mono[ADMAIF_PATHS];
+ struct regmap *regmap;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c
new file mode 100644
index 000000000..dd3a2717f
--- /dev/null
+++ b/sound/soc/tegra/tegra210_adx.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// tegra210_adx.c - Tegra210 ADX driver
+//
+// Copyright (c) 2021-2023 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_adx.h"
+#include "tegra_cif.h"
+
+static const struct reg_default tegra210_adx_reg_defaults[] = {
+ { TEGRA210_ADX_RX_INT_MASK, 0x00000001},
+ { TEGRA210_ADX_RX_CIF_CTRL, 0x00007000},
+ { TEGRA210_ADX_TX_INT_MASK, 0x0000000f },
+ { TEGRA210_ADX_TX1_CIF_CTRL, 0x00007000},
+ { TEGRA210_ADX_TX2_CIF_CTRL, 0x00007000},
+ { TEGRA210_ADX_TX3_CIF_CTRL, 0x00007000},
+ { TEGRA210_ADX_TX4_CIF_CTRL, 0x00007000},
+ { TEGRA210_ADX_CG, 0x1},
+ { TEGRA210_ADX_CFG_RAM_CTRL, 0x00004000},
+};
+
+static void tegra210_adx_write_map_ram(struct tegra210_adx *adx)
+{
+ int i;
+
+ regmap_write(adx->regmap, TEGRA210_ADX_CFG_RAM_CTRL,
+ TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN |
+ TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN |
+ TEGRA210_ADX_CFG_RAM_CTRL_RW_WRITE);
+
+ for (i = 0; i < TEGRA210_ADX_RAM_DEPTH; i++)
+ regmap_write(adx->regmap, TEGRA210_ADX_CFG_RAM_DATA,
+ adx->map[i]);
+
+ regmap_write(adx->regmap, TEGRA210_ADX_IN_BYTE_EN0, adx->byte_mask[0]);
+ regmap_write(adx->regmap, TEGRA210_ADX_IN_BYTE_EN1, adx->byte_mask[1]);
+}
+
+static int tegra210_adx_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct tegra210_adx *adx = snd_soc_dai_get_drvdata(dai);
+ unsigned int val;
+ int err;
+
+ /* Ensure if ADX status is disabled */
+ err = regmap_read_poll_timeout_atomic(adx->regmap, TEGRA210_ADX_STATUS,
+ val, !(val & 0x1), 10, 10000);
+ if (err < 0) {
+ dev_err(dai->dev, "failed to stop ADX, err = %d\n", err);
+ return err;
+ }
+
+ /*
+ * Soft Reset: Below performs module soft reset which clears
+ * all FSM logic, flushes flow control of FIFO and resets the
+ * state register. It also brings module back to disabled
+ * state (without flushing the data in the pipe).
+ */
+ regmap_update_bits(adx->regmap, TEGRA210_ADX_SOFT_RESET,
+ TEGRA210_ADX_SOFT_RESET_SOFT_RESET_MASK,
+ TEGRA210_ADX_SOFT_RESET_SOFT_EN);
+
+ err = regmap_read_poll_timeout(adx->regmap, TEGRA210_ADX_SOFT_RESET,
+ val, !(val & 0x1), 10, 10000);
+ if (err < 0) {
+ dev_err(dai->dev, "failed to reset ADX, err = %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused tegra210_adx_runtime_suspend(struct device *dev)
+{
+ struct tegra210_adx *adx = dev_get_drvdata(dev);
+
+ regcache_cache_only(adx->regmap, true);
+ regcache_mark_dirty(adx->regmap);
+
+ return 0;
+}
+
+static int __maybe_unused tegra210_adx_runtime_resume(struct device *dev)
+{
+ struct tegra210_adx *adx = dev_get_drvdata(dev);
+
+ regcache_cache_only(adx->regmap, false);
+ regcache_sync(adx->regmap);
+
+ tegra210_adx_write_map_ram(adx);
+
+ return 0;
+}
+
+static int tegra210_adx_set_audio_cif(struct snd_soc_dai *dai,
+ unsigned int channels,
+ unsigned int format,
+ unsigned int reg)
+{
+ struct tegra210_adx *adx = snd_soc_dai_get_drvdata(dai);
+ struct tegra_cif_conf cif_conf;
+ int audio_bits;
+
+ memset(&cif_conf, 0, sizeof(struct tegra_cif_conf));
+
+ if (channels < 1 || channels > 16)
+ return -EINVAL;
+
+ switch (format) {
+ case SNDRV_PCM_FORMAT_S8:
+ audio_bits = TEGRA_ACIF_BITS_8;
+ break;
+ 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(adx->regmap, reg, &cif_conf);
+
+ return 0;
+}
+
+static int tegra210_adx_out_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ return tegra210_adx_set_audio_cif(dai, params_channels(params),
+ params_format(params),
+ TEGRA210_ADX_TX1_CIF_CTRL + ((dai->id - 1) * TEGRA210_ADX_AUDIOCIF_CH_STRIDE));
+}
+
+static int tegra210_adx_in_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ return tegra210_adx_set_audio_cif(dai, params_channels(params),
+ params_format(params),
+ TEGRA210_ADX_RX_CIF_CTRL);
+}
+
+static int tegra210_adx_get_byte_map(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_adx *adx = snd_soc_component_get_drvdata(cmpnt);
+ struct soc_mixer_control *mc;
+ unsigned char *bytes_map = (unsigned char *)&adx->map;
+ int enabled;
+
+ mc = (struct soc_mixer_control *)kcontrol->private_value;
+ enabled = adx->byte_mask[mc->reg / 32] & (1 << (mc->reg % 32));
+
+ /*
+ * TODO: Simplify this logic to just return from bytes_map[]
+ *
+ * Presently below is required since bytes_map[] is
+ * tightly packed and cannot store the control value of 256.
+ * Byte mask state is used to know if 256 needs to be returned.
+ * Note that for control value of 256, the put() call stores 0
+ * in the bytes_map[] and disables the corresponding bit in
+ * byte_mask[].
+ */
+ if (enabled)
+ ucontrol->value.integer.value[0] = bytes_map[mc->reg];
+ else
+ ucontrol->value.integer.value[0] = 256;
+
+ return 0;
+}
+
+static int tegra210_adx_put_byte_map(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_adx *adx = snd_soc_component_get_drvdata(cmpnt);
+ 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;
+ unsigned int mask_val = adx->byte_mask[mc->reg / 32];
+
+ if (value >= 0 && value <= 255)
+ mask_val |= (1 << (mc->reg % 32));
+ else
+ mask_val &= ~(1 << (mc->reg % 32));
+
+ if (mask_val == adx->byte_mask[mc->reg / 32])
+ return 0;
+
+ /* Update byte map and slot */
+ bytes_map[mc->reg] = value % 256;
+ adx->byte_mask[mc->reg / 32] = mask_val;
+
+ return 1;
+}
+
+static const struct snd_soc_dai_ops tegra210_adx_in_dai_ops = {
+ .hw_params = tegra210_adx_in_hw_params,
+ .startup = tegra210_adx_startup,
+};
+
+static const struct snd_soc_dai_ops tegra210_adx_out_dai_ops = {
+ .hw_params = tegra210_adx_out_hw_params,
+};
+
+#define IN_DAI \
+ { \
+ .name = "ADX-RX-CIF", \
+ .playback = { \
+ .stream_name = "RX-CIF-Playback", \
+ .channels_min = 1, \
+ .channels_max = 16, \
+ .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 = 16, \
+ .rates = SNDRV_PCM_RATE_8000_192000, \
+ .formats = SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ }, \
+ .ops = &tegra210_adx_in_dai_ops, \
+ }
+
+#define OUT_DAI(id) \
+ { \
+ .name = "ADX-TX" #id "-CIF", \
+ .playback = { \
+ .stream_name = "TX" #id "-CIF-Playback",\
+ .channels_min = 1, \
+ .channels_max = 16, \
+ .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" #id "-CIF-Capture", \
+ .channels_min = 1, \
+ .channels_max = 16, \
+ .rates = SNDRV_PCM_RATE_8000_192000, \
+ .formats = SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ }, \
+ .ops = &tegra210_adx_out_dai_ops, \
+ }
+
+static struct snd_soc_dai_driver tegra210_adx_dais[] = {
+ IN_DAI,
+ OUT_DAI(1),
+ OUT_DAI(2),
+ OUT_DAI(3),
+ OUT_DAI(4),
+};
+
+static const struct snd_soc_dapm_widget tegra210_adx_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("RX", NULL, 0, TEGRA210_ADX_ENABLE,
+ TEGRA210_ADX_ENABLE_SHIFT, 0),
+ SND_SOC_DAPM_AIF_OUT("TX1", NULL, 0, TEGRA210_ADX_CTRL, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX2", NULL, 0, TEGRA210_ADX_CTRL, 1, 0),
+ SND_SOC_DAPM_AIF_OUT("TX3", NULL, 0, TEGRA210_ADX_CTRL, 2, 0),
+ SND_SOC_DAPM_AIF_OUT("TX4", NULL, 0, TEGRA210_ADX_CTRL, 3, 0),
+};
+
+#define STREAM_ROUTES(id, sname) \
+ { "XBAR-" sname, NULL, "XBAR-TX" }, \
+ { "RX-CIF-" sname, NULL, "XBAR-" sname }, \
+ { "RX", NULL, "RX-CIF-" sname }, \
+ { "TX" #id, NULL, "RX" }, \
+ { "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 ADX_ROUTES(id) \
+ STREAM_ROUTES(id, "Playback"), \
+ STREAM_ROUTES(id, "Capture")
+
+#define STREAM_ROUTES(id, sname) \
+ { "XBAR-" sname, NULL, "XBAR-TX" }, \
+ { "RX-CIF-" sname, NULL, "XBAR-" sname }, \
+ { "RX", NULL, "RX-CIF-" sname }, \
+ { "TX" #id, NULL, "RX" }, \
+ { "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 ADX_ROUTES(id) \
+ STREAM_ROUTES(id, "Playback"), \
+ STREAM_ROUTES(id, "Capture")
+
+static const struct snd_soc_dapm_route tegra210_adx_routes[] = {
+ ADX_ROUTES(1),
+ ADX_ROUTES(2),
+ ADX_ROUTES(3),
+ ADX_ROUTES(4),
+};
+
+#define TEGRA210_ADX_BYTE_MAP_CTRL(reg) \
+ SOC_SINGLE_EXT("Byte Map " #reg, reg, 0, 256, 0, \
+ tegra210_adx_get_byte_map, \
+ tegra210_adx_put_byte_map)
+
+static struct snd_kcontrol_new tegra210_adx_controls[] = {
+ TEGRA210_ADX_BYTE_MAP_CTRL(0),
+ TEGRA210_ADX_BYTE_MAP_CTRL(1),
+ TEGRA210_ADX_BYTE_MAP_CTRL(2),
+ TEGRA210_ADX_BYTE_MAP_CTRL(3),
+ TEGRA210_ADX_BYTE_MAP_CTRL(4),
+ TEGRA210_ADX_BYTE_MAP_CTRL(5),
+ TEGRA210_ADX_BYTE_MAP_CTRL(6),
+ TEGRA210_ADX_BYTE_MAP_CTRL(7),
+ TEGRA210_ADX_BYTE_MAP_CTRL(8),
+ TEGRA210_ADX_BYTE_MAP_CTRL(9),
+ TEGRA210_ADX_BYTE_MAP_CTRL(10),
+ TEGRA210_ADX_BYTE_MAP_CTRL(11),
+ TEGRA210_ADX_BYTE_MAP_CTRL(12),
+ TEGRA210_ADX_BYTE_MAP_CTRL(13),
+ TEGRA210_ADX_BYTE_MAP_CTRL(14),
+ TEGRA210_ADX_BYTE_MAP_CTRL(15),
+ TEGRA210_ADX_BYTE_MAP_CTRL(16),
+ TEGRA210_ADX_BYTE_MAP_CTRL(17),
+ TEGRA210_ADX_BYTE_MAP_CTRL(18),
+ TEGRA210_ADX_BYTE_MAP_CTRL(19),
+ TEGRA210_ADX_BYTE_MAP_CTRL(20),
+ TEGRA210_ADX_BYTE_MAP_CTRL(21),
+ TEGRA210_ADX_BYTE_MAP_CTRL(22),
+ TEGRA210_ADX_BYTE_MAP_CTRL(23),
+ TEGRA210_ADX_BYTE_MAP_CTRL(24),
+ TEGRA210_ADX_BYTE_MAP_CTRL(25),
+ TEGRA210_ADX_BYTE_MAP_CTRL(26),
+ TEGRA210_ADX_BYTE_MAP_CTRL(27),
+ TEGRA210_ADX_BYTE_MAP_CTRL(28),
+ TEGRA210_ADX_BYTE_MAP_CTRL(29),
+ TEGRA210_ADX_BYTE_MAP_CTRL(30),
+ TEGRA210_ADX_BYTE_MAP_CTRL(31),
+ TEGRA210_ADX_BYTE_MAP_CTRL(32),
+ TEGRA210_ADX_BYTE_MAP_CTRL(33),
+ TEGRA210_ADX_BYTE_MAP_CTRL(34),
+ TEGRA210_ADX_BYTE_MAP_CTRL(35),
+ TEGRA210_ADX_BYTE_MAP_CTRL(36),
+ TEGRA210_ADX_BYTE_MAP_CTRL(37),
+ TEGRA210_ADX_BYTE_MAP_CTRL(38),
+ TEGRA210_ADX_BYTE_MAP_CTRL(39),
+ TEGRA210_ADX_BYTE_MAP_CTRL(40),
+ TEGRA210_ADX_BYTE_MAP_CTRL(41),
+ TEGRA210_ADX_BYTE_MAP_CTRL(42),
+ TEGRA210_ADX_BYTE_MAP_CTRL(43),
+ TEGRA210_ADX_BYTE_MAP_CTRL(44),
+ TEGRA210_ADX_BYTE_MAP_CTRL(45),
+ TEGRA210_ADX_BYTE_MAP_CTRL(46),
+ TEGRA210_ADX_BYTE_MAP_CTRL(47),
+ TEGRA210_ADX_BYTE_MAP_CTRL(48),
+ TEGRA210_ADX_BYTE_MAP_CTRL(49),
+ TEGRA210_ADX_BYTE_MAP_CTRL(50),
+ TEGRA210_ADX_BYTE_MAP_CTRL(51),
+ TEGRA210_ADX_BYTE_MAP_CTRL(52),
+ TEGRA210_ADX_BYTE_MAP_CTRL(53),
+ TEGRA210_ADX_BYTE_MAP_CTRL(54),
+ TEGRA210_ADX_BYTE_MAP_CTRL(55),
+ TEGRA210_ADX_BYTE_MAP_CTRL(56),
+ TEGRA210_ADX_BYTE_MAP_CTRL(57),
+ TEGRA210_ADX_BYTE_MAP_CTRL(58),
+ TEGRA210_ADX_BYTE_MAP_CTRL(59),
+ TEGRA210_ADX_BYTE_MAP_CTRL(60),
+ TEGRA210_ADX_BYTE_MAP_CTRL(61),
+ TEGRA210_ADX_BYTE_MAP_CTRL(62),
+ TEGRA210_ADX_BYTE_MAP_CTRL(63),
+};
+
+static const struct snd_soc_component_driver tegra210_adx_cmpnt = {
+ .dapm_widgets = tegra210_adx_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra210_adx_widgets),
+ .dapm_routes = tegra210_adx_routes,
+ .num_dapm_routes = ARRAY_SIZE(tegra210_adx_routes),
+ .controls = tegra210_adx_controls,
+ .num_controls = ARRAY_SIZE(tegra210_adx_controls),
+};
+
+static bool tegra210_adx_wr_reg(struct device *dev,
+ unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_ADX_TX_INT_MASK ... TEGRA210_ADX_TX4_CIF_CTRL:
+ case TEGRA210_ADX_RX_INT_MASK ... TEGRA210_ADX_RX_CIF_CTRL:
+ case TEGRA210_ADX_ENABLE ... TEGRA210_ADX_CG:
+ case TEGRA210_ADX_CTRL ... TEGRA210_ADX_IN_BYTE_EN1:
+ case TEGRA210_ADX_CFG_RAM_CTRL ... TEGRA210_ADX_CFG_RAM_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_adx_rd_reg(struct device *dev,
+ unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_ADX_RX_STATUS ... TEGRA210_ADX_CFG_RAM_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_adx_volatile_reg(struct device *dev,
+ unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_ADX_RX_STATUS:
+ case TEGRA210_ADX_RX_INT_STATUS:
+ case TEGRA210_ADX_RX_INT_SET:
+ case TEGRA210_ADX_TX_STATUS:
+ case TEGRA210_ADX_TX_INT_STATUS:
+ case TEGRA210_ADX_TX_INT_SET:
+ case TEGRA210_ADX_SOFT_RESET:
+ case TEGRA210_ADX_STATUS:
+ case TEGRA210_ADX_INT_STATUS:
+ case TEGRA210_ADX_CFG_RAM_CTRL:
+ case TEGRA210_ADX_CFG_RAM_DATA:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static const struct regmap_config tegra210_adx_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA210_ADX_CFG_RAM_DATA,
+ .writeable_reg = tegra210_adx_wr_reg,
+ .readable_reg = tegra210_adx_rd_reg,
+ .volatile_reg = tegra210_adx_volatile_reg,
+ .reg_defaults = tegra210_adx_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tegra210_adx_reg_defaults),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct of_device_id tegra210_adx_of_match[] = {
+ { .compatible = "nvidia,tegra210-adx" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra210_adx_of_match);
+
+static int tegra210_adx_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tegra210_adx *adx;
+ void __iomem *regs;
+ int err;
+
+ adx = devm_kzalloc(dev, sizeof(*adx), GFP_KERNEL);
+ if (!adx)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, adx);
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ adx->regmap = devm_regmap_init_mmio(dev, regs,
+ &tegra210_adx_regmap_config);
+ if (IS_ERR(adx->regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ return PTR_ERR(adx->regmap);
+ }
+
+ regcache_cache_only(adx->regmap, true);
+
+ err = devm_snd_soc_register_component(dev, &tegra210_adx_cmpnt,
+ tegra210_adx_dais,
+ ARRAY_SIZE(tegra210_adx_dais));
+ if (err) {
+ dev_err(dev, "can't register ADX component, err: %d\n", err);
+ return err;
+ }
+
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static int tegra210_adx_platform_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra210_adx_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra210_adx_runtime_suspend,
+ tegra210_adx_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver tegra210_adx_driver = {
+ .driver = {
+ .name = "tegra210-adx",
+ .of_match_table = tegra210_adx_of_match,
+ .pm = &tegra210_adx_pm_ops,
+ },
+ .probe = tegra210_adx_platform_probe,
+ .remove = tegra210_adx_platform_remove,
+};
+module_platform_driver(tegra210_adx_driver);
+
+MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>");
+MODULE_DESCRIPTION("Tegra210 ADX ASoC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/tegra/tegra210_adx.h b/sound/soc/tegra/tegra210_adx.h
new file mode 100644
index 000000000..d7dcb6497
--- /dev/null
+++ b/sound/soc/tegra/tegra210_adx.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra210_adx.h - Definitions for Tegra210 ADX driver
+ *
+ * Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
+ *
+ */
+
+#ifndef __TEGRA210_ADX_H__
+#define __TEGRA210_ADX_H__
+
+/* Register offsets from TEGRA210_ADX*_BASE */
+#define TEGRA210_ADX_RX_STATUS 0x0c
+#define TEGRA210_ADX_RX_INT_STATUS 0x10
+#define TEGRA210_ADX_RX_INT_MASK 0x14
+#define TEGRA210_ADX_RX_INT_SET 0x18
+#define TEGRA210_ADX_RX_INT_CLEAR 0x1c
+#define TEGRA210_ADX_RX_CIF_CTRL 0x20
+#define TEGRA210_ADX_TX_STATUS 0x4c
+#define TEGRA210_ADX_TX_INT_STATUS 0x50
+#define TEGRA210_ADX_TX_INT_MASK 0x54
+#define TEGRA210_ADX_TX_INT_SET 0x58
+#define TEGRA210_ADX_TX_INT_CLEAR 0x5c
+#define TEGRA210_ADX_TX1_CIF_CTRL 0x60
+#define TEGRA210_ADX_TX2_CIF_CTRL 0x64
+#define TEGRA210_ADX_TX3_CIF_CTRL 0x68
+#define TEGRA210_ADX_TX4_CIF_CTRL 0x6c
+#define TEGRA210_ADX_ENABLE 0x80
+#define TEGRA210_ADX_SOFT_RESET 0x84
+#define TEGRA210_ADX_CG 0x88
+#define TEGRA210_ADX_STATUS 0x8c
+#define TEGRA210_ADX_INT_STATUS 0x90
+#define TEGRA210_ADX_CTRL 0xa4
+#define TEGRA210_ADX_IN_BYTE_EN0 0xa8
+#define TEGRA210_ADX_IN_BYTE_EN1 0xac
+#define TEGRA210_ADX_CFG_RAM_CTRL 0xb8
+#define TEGRA210_ADX_CFG_RAM_DATA 0xbc
+
+/* Fields in TEGRA210_ADX_ENABLE */
+#define TEGRA210_ADX_ENABLE_SHIFT 0
+
+/* Fields in TEGRA210_ADX_CFG_RAM_CTRL */
+#define TEGRA210_ADX_CFG_RAM_CTRL_RAM_ADDR_SHIFT 0
+
+#define TEGRA210_ADX_CFG_RAM_CTRL_RW_SHIFT 14
+#define TEGRA210_ADX_CFG_RAM_CTRL_RW_WRITE (1 << TEGRA210_ADX_CFG_RAM_CTRL_RW_SHIFT)
+
+#define TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT 13
+#define TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN (1 << TEGRA210_ADX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT)
+
+#define TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT 12
+#define TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN (1 << TEGRA210_ADX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT)
+
+/* Fields in TEGRA210_ADX_SOFT_RESET */
+#define TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT 0
+#define TEGRA210_ADX_SOFT_RESET_SOFT_RESET_MASK (1 << TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT)
+#define TEGRA210_ADX_SOFT_RESET_SOFT_EN (1 << TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT)
+#define TEGRA210_ADX_SOFT_RESET_SOFT_DEFAULT (0 << TEGRA210_ADX_SOFT_RESET_SOFT_RESET_SHIFT)
+
+#define TEGRA210_ADX_AUDIOCIF_CH_STRIDE 4
+#define TEGRA210_ADX_RAM_DEPTH 16
+#define TEGRA210_ADX_MAP_STREAM_NUMBER_SHIFT 6
+#define TEGRA210_ADX_MAP_WORD_NUMBER_SHIFT 2
+#define TEGRA210_ADX_MAP_BYTE_NUMBER_SHIFT 0
+
+struct tegra210_adx {
+ struct regmap *regmap;
+ unsigned int map[TEGRA210_ADX_RAM_DEPTH];
+ unsigned int byte_mask[2];
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra210_ahub.c b/sound/soc/tegra/tegra210_ahub.c
new file mode 100644
index 000000000..b38d205b6
--- /dev/null
+++ b/sound/soc/tegra/tegra210_ahub.c
@@ -0,0 +1,1441 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// tegra210_ahub.c - Tegra210 AHUB driver
+//
+// Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved.
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include "tegra210_ahub.h"
+
+static int tegra_ahub_get_value_enum(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ struct snd_soc_component *cmpnt = snd_soc_dapm_kcontrol_component(kctl);
+ struct tegra_ahub *ahub = snd_soc_component_get_drvdata(cmpnt);
+ struct soc_enum *e = (struct soc_enum *)kctl->private_value;
+ unsigned int reg, i, bit_pos = 0;
+
+ /*
+ * Find the bit position of current MUX input.
+ * If nothing is set, position would be 0 and it corresponds to 'None'.
+ */
+ for (i = 0; i < ahub->soc_data->reg_count; i++) {
+ unsigned int reg_val;
+
+ reg = e->reg + (TEGRA210_XBAR_PART1_RX * i);
+ reg_val = snd_soc_component_read(cmpnt, reg);
+ reg_val &= ahub->soc_data->mask[i];
+
+ if (reg_val) {
+ bit_pos = ffs(reg_val) +
+ (8 * cmpnt->val_bytes * i);
+ break;
+ }
+ }
+
+ /* Find index related to the item in array *_ahub_mux_texts[] */
+ for (i = 0; i < e->items; i++) {
+ if (bit_pos == e->values[i]) {
+ uctl->value.enumerated.item[0] = i;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int tegra_ahub_put_value_enum(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uctl)
+{
+ struct snd_soc_component *cmpnt = snd_soc_dapm_kcontrol_component(kctl);
+ struct tegra_ahub *ahub = snd_soc_component_get_drvdata(cmpnt);
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctl);
+ struct soc_enum *e = (struct soc_enum *)kctl->private_value;
+ struct snd_soc_dapm_update update[TEGRA_XBAR_UPDATE_MAX_REG] = { };
+ unsigned int *item = uctl->value.enumerated.item;
+ unsigned int value = e->values[item[0]];
+ unsigned int i, bit_pos, reg_idx = 0, reg_val = 0;
+ int change = 0;
+
+ if (item[0] >= e->items)
+ return -EINVAL;
+
+ if (value) {
+ /* Get the register index and value to set */
+ reg_idx = (value - 1) / (8 * cmpnt->val_bytes);
+ bit_pos = (value - 1) % (8 * cmpnt->val_bytes);
+ reg_val = BIT(bit_pos);
+ }
+
+ /*
+ * Run through all parts of a MUX register to find the state changes.
+ * There will be an additional update if new MUX input value is from
+ * different part of the MUX register.
+ */
+ for (i = 0; i < ahub->soc_data->reg_count; i++) {
+ update[i].reg = e->reg + (TEGRA210_XBAR_PART1_RX * i);
+ update[i].val = (i == reg_idx) ? reg_val : 0;
+ update[i].mask = ahub->soc_data->mask[i];
+ update[i].kcontrol = kctl;
+
+ /* Update widget power if state has changed */
+ if (snd_soc_component_test_bits(cmpnt, update[i].reg,
+ update[i].mask,
+ update[i].val))
+ change |= snd_soc_dapm_mux_update_power(dapm, kctl,
+ item[0], e,
+ &update[i]);
+ }
+
+ return change;
+}
+
+static struct snd_soc_dai_driver tegra210_ahub_dais[] = {
+ DAI(ADMAIF1),
+ DAI(ADMAIF2),
+ DAI(ADMAIF3),
+ DAI(ADMAIF4),
+ DAI(ADMAIF5),
+ DAI(ADMAIF6),
+ DAI(ADMAIF7),
+ DAI(ADMAIF8),
+ DAI(ADMAIF9),
+ DAI(ADMAIF10),
+ /* XBAR <-> I2S <-> Codec */
+ DAI(I2S1),
+ DAI(I2S2),
+ DAI(I2S3),
+ DAI(I2S4),
+ DAI(I2S5),
+ /* XBAR <- DMIC <- Codec */
+ DAI(DMIC1),
+ DAI(DMIC2),
+ DAI(DMIC3),
+ /* XBAR -> SFC -> XBAR */
+ DAI(SFC1 RX),
+ DAI(SFC1 TX),
+ DAI(SFC2 RX),
+ DAI(SFC2 TX),
+ DAI(SFC3 RX),
+ DAI(SFC3 TX),
+ DAI(SFC4 RX),
+ DAI(SFC4 TX),
+ /* XBAR -> MVC -> XBAR */
+ DAI(MVC1 RX),
+ DAI(MVC1 TX),
+ DAI(MVC2 RX),
+ DAI(MVC2 TX),
+ /* XBAR -> AMX(4:1) -> XBAR */
+ DAI(AMX1 RX1),
+ DAI(AMX1 RX2),
+ DAI(AMX1 RX3),
+ DAI(AMX1 RX4),
+ DAI(AMX1),
+ DAI(AMX2 RX1),
+ DAI(AMX2 RX2),
+ DAI(AMX2 RX3),
+ DAI(AMX2 RX4),
+ DAI(AMX2),
+ /* XBAR -> ADX(1:4) -> XBAR */
+ DAI(ADX1),
+ DAI(ADX1 TX1),
+ DAI(ADX1 TX2),
+ DAI(ADX1 TX3),
+ DAI(ADX1 TX4),
+ DAI(ADX2),
+ DAI(ADX2 TX1),
+ DAI(ADX2 TX2),
+ DAI(ADX2 TX3),
+ DAI(ADX2 TX4),
+ /* XBAR -> MIXER(10:5) -> XBAR */
+ DAI(MIXER1 RX1),
+ DAI(MIXER1 RX2),
+ DAI(MIXER1 RX3),
+ DAI(MIXER1 RX4),
+ DAI(MIXER1 RX5),
+ DAI(MIXER1 RX6),
+ DAI(MIXER1 RX7),
+ DAI(MIXER1 RX8),
+ DAI(MIXER1 RX9),
+ DAI(MIXER1 RX10),
+ DAI(MIXER1 TX1),
+ DAI(MIXER1 TX2),
+ 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[] = {
+ DAI(ADMAIF1),
+ DAI(ADMAIF2),
+ DAI(ADMAIF3),
+ DAI(ADMAIF4),
+ DAI(ADMAIF5),
+ DAI(ADMAIF6),
+ DAI(ADMAIF7),
+ DAI(ADMAIF8),
+ DAI(ADMAIF9),
+ DAI(ADMAIF10),
+ DAI(ADMAIF11),
+ DAI(ADMAIF12),
+ DAI(ADMAIF13),
+ DAI(ADMAIF14),
+ DAI(ADMAIF15),
+ DAI(ADMAIF16),
+ DAI(ADMAIF17),
+ DAI(ADMAIF18),
+ DAI(ADMAIF19),
+ DAI(ADMAIF20),
+ /* XBAR <-> I2S <-> Codec */
+ DAI(I2S1),
+ DAI(I2S2),
+ DAI(I2S3),
+ DAI(I2S4),
+ DAI(I2S5),
+ DAI(I2S6),
+ /* XBAR <- DMIC <- Codec */
+ DAI(DMIC1),
+ DAI(DMIC2),
+ DAI(DMIC3),
+ DAI(DMIC4),
+ /* XBAR -> DSPK -> Codec */
+ DAI(DSPK1),
+ DAI(DSPK2),
+ /* XBAR -> SFC -> XBAR */
+ DAI(SFC1 RX),
+ DAI(SFC1 TX),
+ DAI(SFC2 RX),
+ DAI(SFC2 TX),
+ DAI(SFC3 RX),
+ DAI(SFC3 TX),
+ DAI(SFC4 RX),
+ DAI(SFC4 TX),
+ /* XBAR -> MVC -> XBAR */
+ DAI(MVC1 RX),
+ DAI(MVC1 TX),
+ DAI(MVC2 RX),
+ DAI(MVC2 TX),
+ /* XBAR -> AMX(4:1) -> XBAR */
+ DAI(AMX1 RX1),
+ DAI(AMX1 RX2),
+ DAI(AMX1 RX3),
+ DAI(AMX1 RX4),
+ DAI(AMX1),
+ DAI(AMX2 RX1),
+ DAI(AMX2 RX2),
+ DAI(AMX2 RX3),
+ DAI(AMX2 RX4),
+ DAI(AMX2),
+ DAI(AMX3 RX1),
+ DAI(AMX3 RX2),
+ DAI(AMX3 RX3),
+ DAI(AMX3 RX4),
+ DAI(AMX3),
+ DAI(AMX4 RX1),
+ DAI(AMX4 RX2),
+ DAI(AMX4 RX3),
+ DAI(AMX4 RX4),
+ DAI(AMX4),
+ /* XBAR -> ADX(1:4) -> XBAR */
+ DAI(ADX1),
+ DAI(ADX1 TX1),
+ DAI(ADX1 TX2),
+ DAI(ADX1 TX3),
+ DAI(ADX1 TX4),
+ DAI(ADX2),
+ DAI(ADX2 TX1),
+ DAI(ADX2 TX2),
+ DAI(ADX2 TX3),
+ DAI(ADX2 TX4),
+ DAI(ADX3),
+ DAI(ADX3 TX1),
+ DAI(ADX3 TX2),
+ DAI(ADX3 TX3),
+ DAI(ADX3 TX4),
+ DAI(ADX4),
+ DAI(ADX4 TX1),
+ DAI(ADX4 TX2),
+ DAI(ADX4 TX3),
+ DAI(ADX4 TX4),
+ /* XBAR -> MIXER1(10:5) -> XBAR */
+ DAI(MIXER1 RX1),
+ DAI(MIXER1 RX2),
+ DAI(MIXER1 RX3),
+ DAI(MIXER1 RX4),
+ DAI(MIXER1 RX5),
+ DAI(MIXER1 RX6),
+ DAI(MIXER1 RX7),
+ DAI(MIXER1 RX8),
+ DAI(MIXER1 RX9),
+ DAI(MIXER1 RX10),
+ DAI(MIXER1 TX1),
+ DAI(MIXER1 TX2),
+ 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[] = {
+ "None",
+ "ADMAIF1",
+ "ADMAIF2",
+ "ADMAIF3",
+ "ADMAIF4",
+ "ADMAIF5",
+ "ADMAIF6",
+ "ADMAIF7",
+ "ADMAIF8",
+ "ADMAIF9",
+ "ADMAIF10",
+ "I2S1",
+ "I2S2",
+ "I2S3",
+ "I2S4",
+ "I2S5",
+ "DMIC1",
+ "DMIC2",
+ "DMIC3",
+ "SFC1",
+ "SFC2",
+ "SFC3",
+ "SFC4",
+ "MVC1",
+ "MVC2",
+ "AMX1",
+ "AMX2",
+ "ADX1 TX1",
+ "ADX1 TX2",
+ "ADX1 TX3",
+ "ADX1 TX4",
+ "ADX2 TX1",
+ "ADX2 TX2",
+ "ADX2 TX3",
+ "ADX2 TX4",
+ "MIXER1 TX1",
+ "MIXER1 TX2",
+ "MIXER1 TX3",
+ "MIXER1 TX4",
+ "MIXER1 TX5",
+ "OPE1",
+ "OPE2",
+};
+
+static const char * const tegra186_ahub_mux_texts[] = {
+ "None",
+ "ADMAIF1",
+ "ADMAIF2",
+ "ADMAIF3",
+ "ADMAIF4",
+ "ADMAIF5",
+ "ADMAIF6",
+ "ADMAIF7",
+ "ADMAIF8",
+ "ADMAIF9",
+ "ADMAIF10",
+ "ADMAIF11",
+ "ADMAIF12",
+ "ADMAIF13",
+ "ADMAIF14",
+ "ADMAIF15",
+ "ADMAIF16",
+ "I2S1",
+ "I2S2",
+ "I2S3",
+ "I2S4",
+ "I2S5",
+ "I2S6",
+ "ADMAIF17",
+ "ADMAIF18",
+ "ADMAIF19",
+ "ADMAIF20",
+ "DMIC1",
+ "DMIC2",
+ "DMIC3",
+ "DMIC4",
+ "SFC1",
+ "SFC2",
+ "SFC3",
+ "SFC4",
+ "MVC1",
+ "MVC2",
+ "AMX1",
+ "AMX2",
+ "AMX3",
+ "AMX4",
+ "ADX1 TX1",
+ "ADX1 TX2",
+ "ADX1 TX3",
+ "ADX1 TX4",
+ "ADX2 TX1",
+ "ADX2 TX2",
+ "ADX2 TX3",
+ "ADX2 TX4",
+ "ADX3 TX1",
+ "ADX3 TX2",
+ "ADX3 TX3",
+ "ADX3 TX4",
+ "ADX4 TX1",
+ "ADX4 TX2",
+ "ADX4 TX3",
+ "ADX4 TX4",
+ "MIXER1 TX1",
+ "MIXER1 TX2",
+ "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[] = {
+ 0,
+ /* ADMAIF */
+ MUX_VALUE(0, 0),
+ MUX_VALUE(0, 1),
+ MUX_VALUE(0, 2),
+ MUX_VALUE(0, 3),
+ MUX_VALUE(0, 4),
+ MUX_VALUE(0, 5),
+ MUX_VALUE(0, 6),
+ MUX_VALUE(0, 7),
+ MUX_VALUE(0, 8),
+ MUX_VALUE(0, 9),
+ /* I2S */
+ MUX_VALUE(0, 16),
+ MUX_VALUE(0, 17),
+ MUX_VALUE(0, 18),
+ MUX_VALUE(0, 19),
+ MUX_VALUE(0, 20),
+ /* DMIC */
+ MUX_VALUE(2, 18),
+ MUX_VALUE(2, 19),
+ MUX_VALUE(2, 20),
+ /* SFC */
+ MUX_VALUE(0, 24),
+ MUX_VALUE(0, 25),
+ MUX_VALUE(0, 26),
+ MUX_VALUE(0, 27),
+ /* MVC */
+ MUX_VALUE(2, 8),
+ MUX_VALUE(2, 9),
+ /* AMX */
+ MUX_VALUE(1, 8),
+ MUX_VALUE(1, 9),
+ /* ADX */
+ MUX_VALUE(2, 24),
+ MUX_VALUE(2, 25),
+ MUX_VALUE(2, 26),
+ MUX_VALUE(2, 27),
+ MUX_VALUE(2, 28),
+ MUX_VALUE(2, 29),
+ MUX_VALUE(2, 30),
+ MUX_VALUE(2, 31),
+ /* MIXER */
+ MUX_VALUE(1, 0),
+ MUX_VALUE(1, 1),
+ 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[] = {
+ 0,
+ /* ADMAIF */
+ MUX_VALUE(0, 0),
+ MUX_VALUE(0, 1),
+ MUX_VALUE(0, 2),
+ MUX_VALUE(0, 3),
+ MUX_VALUE(0, 4),
+ MUX_VALUE(0, 5),
+ MUX_VALUE(0, 6),
+ MUX_VALUE(0, 7),
+ MUX_VALUE(0, 8),
+ MUX_VALUE(0, 9),
+ MUX_VALUE(0, 10),
+ MUX_VALUE(0, 11),
+ MUX_VALUE(0, 12),
+ MUX_VALUE(0, 13),
+ MUX_VALUE(0, 14),
+ MUX_VALUE(0, 15),
+ /* I2S */
+ MUX_VALUE(0, 16),
+ MUX_VALUE(0, 17),
+ MUX_VALUE(0, 18),
+ MUX_VALUE(0, 19),
+ MUX_VALUE(0, 20),
+ MUX_VALUE(0, 21),
+ /* ADMAIF */
+ MUX_VALUE(3, 16),
+ MUX_VALUE(3, 17),
+ MUX_VALUE(3, 18),
+ MUX_VALUE(3, 19),
+ /* DMIC */
+ MUX_VALUE(2, 18),
+ MUX_VALUE(2, 19),
+ MUX_VALUE(2, 20),
+ MUX_VALUE(2, 21),
+ /* SFC */
+ MUX_VALUE(0, 24),
+ MUX_VALUE(0, 25),
+ MUX_VALUE(0, 26),
+ MUX_VALUE(0, 27),
+ /* MVC */
+ MUX_VALUE(2, 8),
+ MUX_VALUE(2, 9),
+ /* AMX */
+ MUX_VALUE(1, 8),
+ MUX_VALUE(1, 9),
+ MUX_VALUE(1, 10),
+ MUX_VALUE(1, 11),
+ /* ADX */
+ MUX_VALUE(2, 24),
+ MUX_VALUE(2, 25),
+ MUX_VALUE(2, 26),
+ MUX_VALUE(2, 27),
+ MUX_VALUE(2, 28),
+ MUX_VALUE(2, 29),
+ MUX_VALUE(2, 30),
+ MUX_VALUE(2, 31),
+ MUX_VALUE(3, 0),
+ MUX_VALUE(3, 1),
+ MUX_VALUE(3, 2),
+ MUX_VALUE(3, 3),
+ MUX_VALUE(3, 4),
+ MUX_VALUE(3, 5),
+ MUX_VALUE(3, 6),
+ MUX_VALUE(3, 7),
+ /* MIXER */
+ MUX_VALUE(1, 0),
+ MUX_VALUE(1, 1),
+ 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 */
+MUX_ENUM_CTRL_DECL(t210_admaif1_tx, 0x00);
+MUX_ENUM_CTRL_DECL(t210_admaif2_tx, 0x01);
+MUX_ENUM_CTRL_DECL(t210_admaif3_tx, 0x02);
+MUX_ENUM_CTRL_DECL(t210_admaif4_tx, 0x03);
+MUX_ENUM_CTRL_DECL(t210_admaif5_tx, 0x04);
+MUX_ENUM_CTRL_DECL(t210_admaif6_tx, 0x05);
+MUX_ENUM_CTRL_DECL(t210_admaif7_tx, 0x06);
+MUX_ENUM_CTRL_DECL(t210_admaif8_tx, 0x07);
+MUX_ENUM_CTRL_DECL(t210_admaif9_tx, 0x08);
+MUX_ENUM_CTRL_DECL(t210_admaif10_tx, 0x09);
+MUX_ENUM_CTRL_DECL(t210_i2s1_tx, 0x10);
+MUX_ENUM_CTRL_DECL(t210_i2s2_tx, 0x11);
+MUX_ENUM_CTRL_DECL(t210_i2s3_tx, 0x12);
+MUX_ENUM_CTRL_DECL(t210_i2s4_tx, 0x13);
+MUX_ENUM_CTRL_DECL(t210_i2s5_tx, 0x14);
+MUX_ENUM_CTRL_DECL(t210_sfc1_tx, 0x18);
+MUX_ENUM_CTRL_DECL(t210_sfc2_tx, 0x19);
+MUX_ENUM_CTRL_DECL(t210_sfc3_tx, 0x1a);
+MUX_ENUM_CTRL_DECL(t210_sfc4_tx, 0x1b);
+MUX_ENUM_CTRL_DECL(t210_mvc1_tx, 0x48);
+MUX_ENUM_CTRL_DECL(t210_mvc2_tx, 0x49);
+MUX_ENUM_CTRL_DECL(t210_amx11_tx, 0x50);
+MUX_ENUM_CTRL_DECL(t210_amx12_tx, 0x51);
+MUX_ENUM_CTRL_DECL(t210_amx13_tx, 0x52);
+MUX_ENUM_CTRL_DECL(t210_amx14_tx, 0x53);
+MUX_ENUM_CTRL_DECL(t210_amx21_tx, 0x54);
+MUX_ENUM_CTRL_DECL(t210_amx22_tx, 0x55);
+MUX_ENUM_CTRL_DECL(t210_amx23_tx, 0x56);
+MUX_ENUM_CTRL_DECL(t210_amx24_tx, 0x57);
+MUX_ENUM_CTRL_DECL(t210_adx1_tx, 0x58);
+MUX_ENUM_CTRL_DECL(t210_adx2_tx, 0x59);
+MUX_ENUM_CTRL_DECL(t210_mixer11_tx, 0x20);
+MUX_ENUM_CTRL_DECL(t210_mixer12_tx, 0x21);
+MUX_ENUM_CTRL_DECL(t210_mixer13_tx, 0x22);
+MUX_ENUM_CTRL_DECL(t210_mixer14_tx, 0x23);
+MUX_ENUM_CTRL_DECL(t210_mixer15_tx, 0x24);
+MUX_ENUM_CTRL_DECL(t210_mixer16_tx, 0x25);
+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);
+MUX_ENUM_CTRL_DECL_186(t186_admaif2_tx, 0x01);
+MUX_ENUM_CTRL_DECL_186(t186_admaif3_tx, 0x02);
+MUX_ENUM_CTRL_DECL_186(t186_admaif4_tx, 0x03);
+MUX_ENUM_CTRL_DECL_186(t186_admaif5_tx, 0x04);
+MUX_ENUM_CTRL_DECL_186(t186_admaif6_tx, 0x05);
+MUX_ENUM_CTRL_DECL_186(t186_admaif7_tx, 0x06);
+MUX_ENUM_CTRL_DECL_186(t186_admaif8_tx, 0x07);
+MUX_ENUM_CTRL_DECL_186(t186_admaif9_tx, 0x08);
+MUX_ENUM_CTRL_DECL_186(t186_admaif10_tx, 0x09);
+MUX_ENUM_CTRL_DECL_186(t186_i2s1_tx, 0x10);
+MUX_ENUM_CTRL_DECL_186(t186_i2s2_tx, 0x11);
+MUX_ENUM_CTRL_DECL_186(t186_i2s3_tx, 0x12);
+MUX_ENUM_CTRL_DECL_186(t186_i2s4_tx, 0x13);
+MUX_ENUM_CTRL_DECL_186(t186_i2s5_tx, 0x14);
+MUX_ENUM_CTRL_DECL_186(t186_admaif11_tx, 0x0a);
+MUX_ENUM_CTRL_DECL_186(t186_admaif12_tx, 0x0b);
+MUX_ENUM_CTRL_DECL_186(t186_admaif13_tx, 0x0c);
+MUX_ENUM_CTRL_DECL_186(t186_admaif14_tx, 0x0d);
+MUX_ENUM_CTRL_DECL_186(t186_admaif15_tx, 0x0e);
+MUX_ENUM_CTRL_DECL_186(t186_admaif16_tx, 0x0f);
+MUX_ENUM_CTRL_DECL_186(t186_i2s6_tx, 0x15);
+MUX_ENUM_CTRL_DECL_186(t186_dspk1_tx, 0x30);
+MUX_ENUM_CTRL_DECL_186(t186_dspk2_tx, 0x31);
+MUX_ENUM_CTRL_DECL_186(t186_admaif17_tx, 0x68);
+MUX_ENUM_CTRL_DECL_186(t186_admaif18_tx, 0x69);
+MUX_ENUM_CTRL_DECL_186(t186_admaif19_tx, 0x6a);
+MUX_ENUM_CTRL_DECL_186(t186_admaif20_tx, 0x6b);
+MUX_ENUM_CTRL_DECL_186(t186_sfc1_tx, 0x18);
+MUX_ENUM_CTRL_DECL_186(t186_sfc2_tx, 0x19);
+MUX_ENUM_CTRL_DECL_186(t186_sfc3_tx, 0x1a);
+MUX_ENUM_CTRL_DECL_186(t186_sfc4_tx, 0x1b);
+MUX_ENUM_CTRL_DECL_186(t186_mvc1_tx, 0x48);
+MUX_ENUM_CTRL_DECL_186(t186_mvc2_tx, 0x49);
+MUX_ENUM_CTRL_DECL_186(t186_amx11_tx, 0x50);
+MUX_ENUM_CTRL_DECL_186(t186_amx12_tx, 0x51);
+MUX_ENUM_CTRL_DECL_186(t186_amx13_tx, 0x52);
+MUX_ENUM_CTRL_DECL_186(t186_amx14_tx, 0x53);
+MUX_ENUM_CTRL_DECL_186(t186_amx21_tx, 0x54);
+MUX_ENUM_CTRL_DECL_186(t186_amx22_tx, 0x55);
+MUX_ENUM_CTRL_DECL_186(t186_amx23_tx, 0x56);
+MUX_ENUM_CTRL_DECL_186(t186_amx24_tx, 0x57);
+MUX_ENUM_CTRL_DECL_186(t186_amx31_tx, 0x58);
+MUX_ENUM_CTRL_DECL_186(t186_amx32_tx, 0x59);
+MUX_ENUM_CTRL_DECL_186(t186_amx33_tx, 0x5a);
+MUX_ENUM_CTRL_DECL_186(t186_amx34_tx, 0x5b);
+MUX_ENUM_CTRL_DECL_186(t186_amx41_tx, 0x64);
+MUX_ENUM_CTRL_DECL_186(t186_amx42_tx, 0x65);
+MUX_ENUM_CTRL_DECL_186(t186_amx43_tx, 0x66);
+MUX_ENUM_CTRL_DECL_186(t186_amx44_tx, 0x67);
+MUX_ENUM_CTRL_DECL_186(t186_adx1_tx, 0x60);
+MUX_ENUM_CTRL_DECL_186(t186_adx2_tx, 0x61);
+MUX_ENUM_CTRL_DECL_186(t186_adx3_tx, 0x62);
+MUX_ENUM_CTRL_DECL_186(t186_adx4_tx, 0x63);
+MUX_ENUM_CTRL_DECL_186(t186_mixer11_tx, 0x20);
+MUX_ENUM_CTRL_DECL_186(t186_mixer12_tx, 0x21);
+MUX_ENUM_CTRL_DECL_186(t186_mixer13_tx, 0x22);
+MUX_ENUM_CTRL_DECL_186(t186_mixer14_tx, 0x23);
+MUX_ENUM_CTRL_DECL_186(t186_mixer15_tx, 0x24);
+MUX_ENUM_CTRL_DECL_186(t186_mixer16_tx, 0x25);
+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
+ * calculation of tegra210_ahub_codec.num_dapm_widgets near the end of
+ * tegra210_ahub_probe()
+ */
+static const struct snd_soc_dapm_widget tegra210_ahub_widgets[] = {
+ WIDGETS("ADMAIF1", t210_admaif1_tx),
+ WIDGETS("ADMAIF2", t210_admaif2_tx),
+ WIDGETS("ADMAIF3", t210_admaif3_tx),
+ WIDGETS("ADMAIF4", t210_admaif4_tx),
+ WIDGETS("ADMAIF5", t210_admaif5_tx),
+ WIDGETS("ADMAIF6", t210_admaif6_tx),
+ WIDGETS("ADMAIF7", t210_admaif7_tx),
+ WIDGETS("ADMAIF8", t210_admaif8_tx),
+ WIDGETS("ADMAIF9", t210_admaif9_tx),
+ WIDGETS("ADMAIF10", t210_admaif10_tx),
+ WIDGETS("I2S1", t210_i2s1_tx),
+ WIDGETS("I2S2", t210_i2s2_tx),
+ WIDGETS("I2S3", t210_i2s3_tx),
+ WIDGETS("I2S4", t210_i2s4_tx),
+ WIDGETS("I2S5", t210_i2s5_tx),
+ TX_WIDGETS("DMIC1"),
+ TX_WIDGETS("DMIC2"),
+ TX_WIDGETS("DMIC3"),
+ WIDGETS("SFC1", t210_sfc1_tx),
+ WIDGETS("SFC2", t210_sfc2_tx),
+ WIDGETS("SFC3", t210_sfc3_tx),
+ WIDGETS("SFC4", t210_sfc4_tx),
+ WIDGETS("MVC1", t210_mvc1_tx),
+ WIDGETS("MVC2", t210_mvc2_tx),
+ WIDGETS("AMX1 RX1", t210_amx11_tx),
+ WIDGETS("AMX1 RX2", t210_amx12_tx),
+ WIDGETS("AMX1 RX3", t210_amx13_tx),
+ WIDGETS("AMX1 RX4", t210_amx14_tx),
+ WIDGETS("AMX2 RX1", t210_amx21_tx),
+ WIDGETS("AMX2 RX2", t210_amx22_tx),
+ WIDGETS("AMX2 RX3", t210_amx23_tx),
+ WIDGETS("AMX2 RX4", t210_amx24_tx),
+ TX_WIDGETS("AMX1"),
+ TX_WIDGETS("AMX2"),
+ WIDGETS("ADX1", t210_adx1_tx),
+ WIDGETS("ADX2", t210_adx2_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"),
+ WIDGETS("MIXER1 RX1", t210_mixer11_tx),
+ WIDGETS("MIXER1 RX2", t210_mixer12_tx),
+ WIDGETS("MIXER1 RX3", t210_mixer13_tx),
+ WIDGETS("MIXER1 RX4", t210_mixer14_tx),
+ WIDGETS("MIXER1 RX5", t210_mixer15_tx),
+ WIDGETS("MIXER1 RX6", t210_mixer16_tx),
+ WIDGETS("MIXER1 RX7", t210_mixer17_tx),
+ WIDGETS("MIXER1 RX8", t210_mixer18_tx),
+ WIDGETS("MIXER1 RX9", t210_mixer19_tx),
+ WIDGETS("MIXER1 RX10", t210_mixer110_tx),
+ TX_WIDGETS("MIXER1 TX1"),
+ TX_WIDGETS("MIXER1 TX2"),
+ 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[] = {
+ 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", t186_admaif17_tx),
+ WIDGETS("ADMAIF18", t186_admaif18_tx),
+ WIDGETS("ADMAIF19", t186_admaif19_tx),
+ WIDGETS("ADMAIF20", t186_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", t186_mvc1_tx),
+ WIDGETS("MVC2", t186_mvc2_tx),
+ WIDGETS("AMX1 RX1", t186_amx11_tx),
+ WIDGETS("AMX1 RX2", t186_amx12_tx),
+ WIDGETS("AMX1 RX3", t186_amx13_tx),
+ WIDGETS("AMX1 RX4", t186_amx14_tx),
+ WIDGETS("AMX2 RX1", t186_amx21_tx),
+ WIDGETS("AMX2 RX2", t186_amx22_tx),
+ WIDGETS("AMX2 RX3", t186_amx23_tx),
+ WIDGETS("AMX2 RX4", t186_amx24_tx),
+ WIDGETS("AMX3 RX1", t186_amx31_tx),
+ WIDGETS("AMX3 RX2", t186_amx32_tx),
+ WIDGETS("AMX3 RX3", t186_amx33_tx),
+ WIDGETS("AMX3 RX4", t186_amx34_tx),
+ WIDGETS("AMX4 RX1", t186_amx41_tx),
+ WIDGETS("AMX4 RX2", t186_amx42_tx),
+ WIDGETS("AMX4 RX3", t186_amx43_tx),
+ WIDGETS("AMX4 RX4", t186_amx44_tx),
+ TX_WIDGETS("AMX1"),
+ TX_WIDGETS("AMX2"),
+ TX_WIDGETS("AMX3"),
+ TX_WIDGETS("AMX4"),
+ WIDGETS("ADX1", t186_adx1_tx),
+ WIDGETS("ADX2", t186_adx2_tx),
+ WIDGETS("ADX3", t186_adx3_tx),
+ WIDGETS("ADX4", t186_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", 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) \
+ { name " XBAR-TX", NULL, name " Mux" }, \
+ { name " Mux", "ADMAIF1", "ADMAIF1 XBAR-RX" }, \
+ { name " Mux", "ADMAIF2", "ADMAIF2 XBAR-RX" }, \
+ { name " Mux", "ADMAIF3", "ADMAIF3 XBAR-RX" }, \
+ { name " Mux", "ADMAIF4", "ADMAIF4 XBAR-RX" }, \
+ { name " Mux", "ADMAIF5", "ADMAIF5 XBAR-RX" }, \
+ { name " Mux", "ADMAIF6", "ADMAIF6 XBAR-RX" }, \
+ { name " Mux", "ADMAIF7", "ADMAIF7 XBAR-RX" }, \
+ { name " Mux", "ADMAIF8", "ADMAIF8 XBAR-RX" }, \
+ { name " Mux", "ADMAIF9", "ADMAIF9 XBAR-RX" }, \
+ { name " Mux", "ADMAIF10", "ADMAIF10 XBAR-RX" }, \
+ { name " Mux", "I2S1", "I2S1 XBAR-RX" }, \
+ { name " Mux", "I2S2", "I2S2 XBAR-RX" }, \
+ { name " Mux", "I2S3", "I2S3 XBAR-RX" }, \
+ { name " Mux", "I2S4", "I2S4 XBAR-RX" }, \
+ { name " Mux", "I2S5", "I2S5 XBAR-RX" }, \
+ { name " Mux", "DMIC1", "DMIC1 XBAR-RX" }, \
+ { name " Mux", "DMIC2", "DMIC2 XBAR-RX" }, \
+ { name " Mux", "DMIC3", "DMIC3 XBAR-RX" }, \
+ { name " Mux", "SFC1", "SFC1 XBAR-RX" }, \
+ { name " Mux", "SFC2", "SFC2 XBAR-RX" }, \
+ { name " Mux", "SFC3", "SFC3 XBAR-RX" }, \
+ { name " Mux", "SFC4", "SFC4 XBAR-RX" }, \
+ { name " Mux", "MVC1", "MVC1 XBAR-RX" }, \
+ { name " Mux", "MVC2", "MVC2 XBAR-RX" }, \
+ { name " Mux", "AMX1", "AMX1 XBAR-RX" }, \
+ { name " Mux", "AMX2", "AMX2 XBAR-RX" }, \
+ { name " Mux", "ADX1 TX1", "ADX1 TX1 XBAR-RX" }, \
+ { name " Mux", "ADX1 TX2", "ADX1 TX2 XBAR-RX" }, \
+ { name " Mux", "ADX1 TX3", "ADX1 TX3 XBAR-RX" }, \
+ { name " Mux", "ADX1 TX4", "ADX1 TX4 XBAR-RX" }, \
+ { name " Mux", "ADX2 TX1", "ADX2 TX1 XBAR-RX" }, \
+ { name " Mux", "ADX2 TX2", "ADX2 TX2 XBAR-RX" }, \
+ { name " Mux", "ADX2 TX3", "ADX2 TX3 XBAR-RX" }, \
+ { name " Mux", "ADX2 TX4", "ADX2 TX4 XBAR-RX" }, \
+ { name " Mux", "MIXER1 TX1", "MIXER1 TX1 XBAR-RX" }, \
+ { 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", "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" }, \
+ { name " Mux", "ADMAIF12", "ADMAIF12 XBAR-RX" }, \
+ { name " Mux", "ADMAIF13", "ADMAIF13 XBAR-RX" }, \
+ { name " Mux", "ADMAIF14", "ADMAIF14 XBAR-RX" }, \
+ { name " Mux", "ADMAIF15", "ADMAIF15 XBAR-RX" }, \
+ { name " Mux", "ADMAIF16", "ADMAIF16 XBAR-RX" }, \
+ { name " Mux", "ADMAIF17", "ADMAIF17 XBAR-RX" }, \
+ { name " Mux", "ADMAIF18", "ADMAIF18 XBAR-RX" }, \
+ { name " Mux", "ADMAIF19", "ADMAIF19 XBAR-RX" }, \
+ { name " Mux", "ADMAIF20", "ADMAIF20 XBAR-RX" }, \
+ { name " Mux", "I2S6", "I2S6 XBAR-RX" }, \
+ { name " Mux", "DMIC4", "DMIC4 XBAR-RX" }, \
+ { name " Mux", "AMX3", "AMX3 XBAR-RX" }, \
+ { name " Mux", "AMX4", "AMX4 XBAR-RX" }, \
+ { name " Mux", "ADX3 TX1", "ADX3 TX1 XBAR-RX" }, \
+ { name " Mux", "ADX3 TX2", "ADX3 TX2 XBAR-RX" }, \
+ { name " Mux", "ADX3 TX3", "ADX3 TX3 XBAR-RX" }, \
+ { name " Mux", "ADX3 TX4", "ADX3 TX4 XBAR-RX" }, \
+ { 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", "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) \
+ TEGRA210_ONLY_MUX_ROUTES(name)
+
+#define TEGRA186_MUX_ROUTES(name) \
+ TEGRA_COMMON_MUX_ROUTES(name) \
+ TEGRA186_ONLY_MUX_ROUTES(name)
+
+/* Connect FEs with XBAR */
+#define TEGRA_FE_ROUTES(name) \
+ { name " XBAR-Playback", NULL, name " Playback" }, \
+ { name " XBAR-RX", NULL, name " XBAR-Playback"}, \
+ { name " XBAR-Capture", NULL, name " XBAR-TX" }, \
+ { name " Capture", NULL, name " XBAR-Capture" },
+
+/*
+ * The number of entries in, and order of, this array is closely tied to the
+ * calculation of tegra210_ahub_codec.num_dapm_routes near the end of
+ * tegra210_ahub_probe()
+ */
+static const struct snd_soc_dapm_route tegra210_ahub_routes[] = {
+ TEGRA_FE_ROUTES("ADMAIF1")
+ TEGRA_FE_ROUTES("ADMAIF2")
+ TEGRA_FE_ROUTES("ADMAIF3")
+ TEGRA_FE_ROUTES("ADMAIF4")
+ TEGRA_FE_ROUTES("ADMAIF5")
+ TEGRA_FE_ROUTES("ADMAIF6")
+ TEGRA_FE_ROUTES("ADMAIF7")
+ TEGRA_FE_ROUTES("ADMAIF8")
+ TEGRA_FE_ROUTES("ADMAIF9")
+ TEGRA_FE_ROUTES("ADMAIF10")
+ TEGRA210_MUX_ROUTES("ADMAIF1")
+ TEGRA210_MUX_ROUTES("ADMAIF2")
+ TEGRA210_MUX_ROUTES("ADMAIF3")
+ TEGRA210_MUX_ROUTES("ADMAIF4")
+ TEGRA210_MUX_ROUTES("ADMAIF5")
+ TEGRA210_MUX_ROUTES("ADMAIF6")
+ TEGRA210_MUX_ROUTES("ADMAIF7")
+ TEGRA210_MUX_ROUTES("ADMAIF8")
+ TEGRA210_MUX_ROUTES("ADMAIF9")
+ TEGRA210_MUX_ROUTES("ADMAIF10")
+ TEGRA210_MUX_ROUTES("I2S1")
+ TEGRA210_MUX_ROUTES("I2S2")
+ TEGRA210_MUX_ROUTES("I2S3")
+ TEGRA210_MUX_ROUTES("I2S4")
+ TEGRA210_MUX_ROUTES("I2S5")
+ TEGRA210_MUX_ROUTES("SFC1")
+ TEGRA210_MUX_ROUTES("SFC2")
+ TEGRA210_MUX_ROUTES("SFC3")
+ TEGRA210_MUX_ROUTES("SFC4")
+ TEGRA210_MUX_ROUTES("MVC1")
+ TEGRA210_MUX_ROUTES("MVC2")
+ TEGRA210_MUX_ROUTES("AMX1 RX1")
+ TEGRA210_MUX_ROUTES("AMX1 RX2")
+ TEGRA210_MUX_ROUTES("AMX1 RX3")
+ TEGRA210_MUX_ROUTES("AMX1 RX4")
+ TEGRA210_MUX_ROUTES("AMX2 RX1")
+ TEGRA210_MUX_ROUTES("AMX2 RX2")
+ TEGRA210_MUX_ROUTES("AMX2 RX3")
+ TEGRA210_MUX_ROUTES("AMX2 RX4")
+ TEGRA210_MUX_ROUTES("ADX1")
+ TEGRA210_MUX_ROUTES("ADX2")
+ TEGRA210_MUX_ROUTES("MIXER1 RX1")
+ TEGRA210_MUX_ROUTES("MIXER1 RX2")
+ TEGRA210_MUX_ROUTES("MIXER1 RX3")
+ TEGRA210_MUX_ROUTES("MIXER1 RX4")
+ TEGRA210_MUX_ROUTES("MIXER1 RX5")
+ TEGRA210_MUX_ROUTES("MIXER1 RX6")
+ TEGRA210_MUX_ROUTES("MIXER1 RX7")
+ 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[] = {
+ TEGRA_FE_ROUTES("ADMAIF1")
+ TEGRA_FE_ROUTES("ADMAIF2")
+ TEGRA_FE_ROUTES("ADMAIF3")
+ TEGRA_FE_ROUTES("ADMAIF4")
+ TEGRA_FE_ROUTES("ADMAIF5")
+ TEGRA_FE_ROUTES("ADMAIF6")
+ TEGRA_FE_ROUTES("ADMAIF7")
+ TEGRA_FE_ROUTES("ADMAIF8")
+ TEGRA_FE_ROUTES("ADMAIF9")
+ TEGRA_FE_ROUTES("ADMAIF10")
+ TEGRA_FE_ROUTES("ADMAIF11")
+ TEGRA_FE_ROUTES("ADMAIF12")
+ TEGRA_FE_ROUTES("ADMAIF13")
+ TEGRA_FE_ROUTES("ADMAIF14")
+ TEGRA_FE_ROUTES("ADMAIF15")
+ TEGRA_FE_ROUTES("ADMAIF16")
+ TEGRA_FE_ROUTES("ADMAIF17")
+ TEGRA_FE_ROUTES("ADMAIF18")
+ TEGRA_FE_ROUTES("ADMAIF19")
+ TEGRA_FE_ROUTES("ADMAIF20")
+ TEGRA186_MUX_ROUTES("ADMAIF1")
+ TEGRA186_MUX_ROUTES("ADMAIF2")
+ TEGRA186_MUX_ROUTES("ADMAIF3")
+ TEGRA186_MUX_ROUTES("ADMAIF4")
+ TEGRA186_MUX_ROUTES("ADMAIF5")
+ TEGRA186_MUX_ROUTES("ADMAIF6")
+ TEGRA186_MUX_ROUTES("ADMAIF7")
+ TEGRA186_MUX_ROUTES("ADMAIF8")
+ TEGRA186_MUX_ROUTES("ADMAIF9")
+ TEGRA186_MUX_ROUTES("ADMAIF10")
+ TEGRA186_MUX_ROUTES("ADMAIF11")
+ TEGRA186_MUX_ROUTES("ADMAIF12")
+ TEGRA186_MUX_ROUTES("ADMAIF13")
+ TEGRA186_MUX_ROUTES("ADMAIF14")
+ TEGRA186_MUX_ROUTES("ADMAIF15")
+ TEGRA186_MUX_ROUTES("ADMAIF16")
+ TEGRA186_MUX_ROUTES("ADMAIF17")
+ TEGRA186_MUX_ROUTES("ADMAIF18")
+ TEGRA186_MUX_ROUTES("ADMAIF19")
+ TEGRA186_MUX_ROUTES("ADMAIF20")
+ TEGRA186_MUX_ROUTES("I2S1")
+ TEGRA186_MUX_ROUTES("I2S2")
+ TEGRA186_MUX_ROUTES("I2S3")
+ TEGRA186_MUX_ROUTES("I2S4")
+ TEGRA186_MUX_ROUTES("I2S5")
+ TEGRA186_MUX_ROUTES("I2S6")
+ TEGRA186_MUX_ROUTES("DSPK1")
+ TEGRA186_MUX_ROUTES("DSPK2")
+ TEGRA186_MUX_ROUTES("SFC1")
+ TEGRA186_MUX_ROUTES("SFC2")
+ TEGRA186_MUX_ROUTES("SFC3")
+ TEGRA186_MUX_ROUTES("SFC4")
+ TEGRA186_MUX_ROUTES("MVC1")
+ TEGRA186_MUX_ROUTES("MVC2")
+ TEGRA186_MUX_ROUTES("AMX1 RX1")
+ TEGRA186_MUX_ROUTES("AMX1 RX2")
+ TEGRA186_MUX_ROUTES("AMX1 RX3")
+ TEGRA186_MUX_ROUTES("AMX1 RX4")
+ TEGRA186_MUX_ROUTES("AMX2 RX1")
+ TEGRA186_MUX_ROUTES("AMX2 RX2")
+ TEGRA186_MUX_ROUTES("AMX2 RX3")
+ TEGRA186_MUX_ROUTES("AMX2 RX4")
+ TEGRA186_MUX_ROUTES("AMX3 RX1")
+ TEGRA186_MUX_ROUTES("AMX3 RX2")
+ TEGRA186_MUX_ROUTES("AMX3 RX3")
+ TEGRA186_MUX_ROUTES("AMX3 RX4")
+ TEGRA186_MUX_ROUTES("AMX4 RX1")
+ TEGRA186_MUX_ROUTES("AMX4 RX2")
+ TEGRA186_MUX_ROUTES("AMX4 RX3")
+ TEGRA186_MUX_ROUTES("AMX4 RX4")
+ TEGRA186_MUX_ROUTES("ADX1")
+ TEGRA186_MUX_ROUTES("ADX2")
+ TEGRA186_MUX_ROUTES("ADX3")
+ TEGRA186_MUX_ROUTES("ADX4")
+ TEGRA186_MUX_ROUTES("MIXER1 RX1")
+ TEGRA186_MUX_ROUTES("MIXER1 RX2")
+ TEGRA186_MUX_ROUTES("MIXER1 RX3")
+ TEGRA186_MUX_ROUTES("MIXER1 RX4")
+ TEGRA186_MUX_ROUTES("MIXER1 RX5")
+ TEGRA186_MUX_ROUTES("MIXER1 RX6")
+ TEGRA186_MUX_ROUTES("MIXER1 RX7")
+ 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 = {
+ .dapm_widgets = tegra210_ahub_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra210_ahub_widgets),
+ .dapm_routes = tegra210_ahub_routes,
+ .num_dapm_routes = ARRAY_SIZE(tegra210_ahub_routes),
+};
+
+static const struct snd_soc_component_driver tegra186_ahub_component = {
+ .dapm_widgets = tegra186_ahub_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra186_ahub_widgets),
+ .dapm_routes = tegra186_ahub_routes,
+ .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,
+ .reg_stride = 4,
+ .max_register = TEGRA210_MAX_REGISTER_ADDR,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct regmap_config tegra186_ahub_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = TEGRA186_MAX_REGISTER_ADDR,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct tegra_ahub_soc_data soc_data_tegra210 = {
+ .cmpnt_drv = &tegra210_ahub_component,
+ .dai_drv = tegra210_ahub_dais,
+ .num_dais = ARRAY_SIZE(tegra210_ahub_dais),
+ .regmap_config = &tegra210_ahub_regmap_config,
+ .mask[0] = TEGRA210_XBAR_REG_MASK_0,
+ .mask[1] = TEGRA210_XBAR_REG_MASK_1,
+ .mask[2] = TEGRA210_XBAR_REG_MASK_2,
+ .mask[3] = TEGRA210_XBAR_REG_MASK_3,
+ .reg_count = TEGRA210_XBAR_UPDATE_MAX_REG,
+};
+
+static const struct tegra_ahub_soc_data soc_data_tegra186 = {
+ .cmpnt_drv = &tegra186_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 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);
+
+static int __maybe_unused tegra_ahub_runtime_suspend(struct device *dev)
+{
+ struct tegra_ahub *ahub = dev_get_drvdata(dev);
+
+ regcache_cache_only(ahub->regmap, true);
+ regcache_mark_dirty(ahub->regmap);
+
+ clk_disable_unprepare(ahub->clk);
+
+ return 0;
+}
+
+static int __maybe_unused tegra_ahub_runtime_resume(struct device *dev)
+{
+ struct tegra_ahub *ahub = dev_get_drvdata(dev);
+ int err;
+
+ err = clk_prepare_enable(ahub->clk);
+ if (err) {
+ dev_err(dev, "failed to enable AHUB clock, err: %d\n", err);
+ return err;
+ }
+
+ regcache_cache_only(ahub->regmap, false);
+ regcache_sync(ahub->regmap);
+
+ return 0;
+}
+
+static int tegra_ahub_probe(struct platform_device *pdev)
+{
+ struct tegra_ahub *ahub;
+ void __iomem *regs;
+ int err;
+
+ ahub = devm_kzalloc(&pdev->dev, sizeof(*ahub), GFP_KERNEL);
+ if (!ahub)
+ return -ENOMEM;
+
+ ahub->soc_data = of_device_get_match_data(&pdev->dev);
+
+ platform_set_drvdata(pdev, ahub);
+
+ ahub->clk = devm_clk_get(&pdev->dev, "ahub");
+ if (IS_ERR(ahub->clk)) {
+ dev_err(&pdev->dev, "can't retrieve AHUB clock\n");
+ return PTR_ERR(ahub->clk);
+ }
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ ahub->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
+ ahub->soc_data->regmap_config);
+ if (IS_ERR(ahub->regmap)) {
+ dev_err(&pdev->dev, "regmap init failed\n");
+ return PTR_ERR(ahub->regmap);
+ }
+
+ regcache_cache_only(ahub->regmap, true);
+
+ err = devm_snd_soc_register_component(&pdev->dev,
+ ahub->soc_data->cmpnt_drv,
+ ahub->soc_data->dai_drv,
+ ahub->soc_data->num_dais);
+ if (err) {
+ dev_err(&pdev->dev, "can't register AHUB component, err: %d\n",
+ err);
+ return err;
+ }
+
+ err = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
+ if (err)
+ return err;
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+}
+
+static int tegra_ahub_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra_ahub_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra_ahub_runtime_suspend,
+ tegra_ahub_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver tegra_ahub_driver = {
+ .probe = tegra_ahub_probe,
+ .remove = tegra_ahub_remove,
+ .driver = {
+ .name = "tegra210-ahub",
+ .of_match_table = tegra_ahub_of_match,
+ .pm = &tegra_ahub_pm_ops,
+ },
+};
+module_platform_driver(tegra_ahub_driver);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_AUTHOR("Mohan Kumar <mkumard@nvidia.com>");
+MODULE_DESCRIPTION("Tegra210 ASoC AHUB driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/tegra/tegra210_ahub.h b/sound/soc/tegra/tegra210_ahub.h
new file mode 100644
index 000000000..2728db4d2
--- /dev/null
+++ b/sound/soc/tegra/tegra210_ahub.h
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra210_ahub.h - TEGRA210 AHUB
+ *
+ * Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved.
+ *
+ */
+
+#ifndef __TEGRA210_AHUB__H__
+#define __TEGRA210_AHUB__H__
+
+/* Tegra210 specific */
+#define TEGRA210_XBAR_PART1_RX 0x200
+#define TEGRA210_XBAR_PART2_RX 0x400
+#define TEGRA210_XBAR_RX_STRIDE 0x4
+#define TEGRA210_XBAR_AUDIO_RX_COUNT 90
+#define TEGRA210_XBAR_REG_MASK_0 0xf1f03ff
+#define TEGRA210_XBAR_REG_MASK_1 0x3f30031f
+#define TEGRA210_XBAR_REG_MASK_2 0xff1cf313
+#define TEGRA210_XBAR_REG_MASK_3 0x0
+#define TEGRA210_XBAR_UPDATE_MAX_REG 3
+/* Tegra186 specific */
+#define TEGRA186_XBAR_PART3_RX 0x600
+#define TEGRA186_XBAR_AUDIO_RX_COUNT 115
+#define TEGRA186_XBAR_REG_MASK_0 0xf3fffff
+#define TEGRA186_XBAR_REG_MASK_1 0x3f310f1f
+#define TEGRA186_XBAR_REG_MASK_2 0xff3cf311
+#define TEGRA186_XBAR_REG_MASK_3 0x3f0f00ff
+#define TEGRA186_XBAR_UPDATE_MAX_REG 4
+
+#define TEGRA_XBAR_UPDATE_MAX_REG (TEGRA186_XBAR_UPDATE_MAX_REG)
+
+#define TEGRA186_MAX_REGISTER_ADDR (TEGRA186_XBAR_PART3_RX + \
+ (TEGRA210_XBAR_RX_STRIDE * (TEGRA186_XBAR_AUDIO_RX_COUNT - 1)))
+
+#define TEGRA210_MAX_REGISTER_ADDR (TEGRA210_XBAR_PART2_RX + \
+ (TEGRA210_XBAR_RX_STRIDE * (TEGRA210_XBAR_AUDIO_RX_COUNT - 1)))
+
+#define MUX_REG(id) (TEGRA210_XBAR_RX_STRIDE * (id))
+
+#define MUX_VALUE(npart, nbit) (1 + (nbit) + (npart) * 32)
+
+#define SOC_VALUE_ENUM_WIDE(xreg, shift, xmax, xtexts, xvalues) \
+ { \
+ .reg = xreg, \
+ .shift_l = shift, \
+ .shift_r = shift, \
+ .items = xmax, \
+ .texts = xtexts, \
+ .values = xvalues, \
+ .mask = xmax ? roundup_pow_of_two(xmax) - 1 : 0 \
+ }
+
+#define SOC_VALUE_ENUM_WIDE_DECL(name, xreg, shift, xtexts, xvalues) \
+ static struct soc_enum name = \
+ SOC_VALUE_ENUM_WIDE(xreg, shift, ARRAY_SIZE(xtexts), \
+ xtexts, xvalues)
+
+#define MUX_ENUM_CTRL_DECL(ename, id) \
+ SOC_VALUE_ENUM_WIDE_DECL(ename##_enum, MUX_REG(id), 0, \
+ tegra210_ahub_mux_texts, \
+ tegra210_ahub_mux_values); \
+ static const struct snd_kcontrol_new ename##_control = \
+ SOC_DAPM_ENUM_EXT("Route", ename##_enum, \
+ tegra_ahub_get_value_enum, \
+ tegra_ahub_put_value_enum)
+
+#define MUX_ENUM_CTRL_DECL_186(ename, id) \
+ SOC_VALUE_ENUM_WIDE_DECL(ename##_enum, MUX_REG(id), 0, \
+ tegra186_ahub_mux_texts, \
+ tegra186_ahub_mux_values); \
+ static const struct snd_kcontrol_new ename##_control = \
+ SOC_DAPM_ENUM_EXT("Route", ename##_enum, \
+ 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), \
+ SND_SOC_DAPM_MUX(sname " Mux", SND_SOC_NOPM, 0, 0, \
+ &ename##_control)
+
+#define TX_WIDGETS(sname) \
+ 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)
+
+#define DAI(sname) \
+ { \
+ .name = "XBAR-" #sname, \
+ .playback = { \
+ .stream_name = #sname " XBAR-Playback", \
+ .channels_min = 1, \
+ .channels_max = 16, \
+ .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 = #sname " XBAR-Capture", \
+ .channels_min = 1, \
+ .channels_max = 16, \
+ .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, \
+ }, \
+ }
+
+struct tegra_ahub_soc_data {
+ const struct regmap_config *regmap_config;
+ const struct snd_soc_component_driver *cmpnt_drv;
+ struct snd_soc_dai_driver *dai_drv;
+ unsigned int mask[4];
+ unsigned int reg_count;
+ unsigned int num_dais;
+};
+
+struct tegra_ahub {
+ const struct tegra_ahub_soc_data *soc_data;
+ struct regmap *regmap;
+ struct clk *clk;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra210_amx.c b/sound/soc/tegra/tegra210_amx.c
new file mode 100644
index 000000000..a71c5916d
--- /dev/null
+++ b/sound/soc/tegra/tegra210_amx.c
@@ -0,0 +1,607 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// tegra210_amx.c - Tegra210 AMX driver
+//
+// Copyright (c) 2021-2023 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_amx.h"
+#include "tegra_cif.h"
+
+/*
+ * The counter is in terms of AHUB clock cycles. If a frame is not
+ * received within these clock cycles, the AMX input channel gets
+ * automatically disabled. For now the counter is calculated as a
+ * function of sample rate (8 kHz) and AHUB clock (49.152 MHz).
+ * If later an accurate number is needed, the counter needs to be
+ * calculated at runtime.
+ *
+ * count = ahub_clk / sample_rate
+ */
+#define TEGRA194_MAX_FRAME_IDLE_COUNT 0x1800
+
+#define AMX_CH_REG(id, reg) ((reg) + ((id) * TEGRA210_AMX_AUDIOCIF_CH_STRIDE))
+
+static const struct reg_default tegra210_amx_reg_defaults[] = {
+ { TEGRA210_AMX_RX_INT_MASK, 0x0000000f},
+ { TEGRA210_AMX_RX1_CIF_CTRL, 0x00007000},
+ { TEGRA210_AMX_RX2_CIF_CTRL, 0x00007000},
+ { TEGRA210_AMX_RX3_CIF_CTRL, 0x00007000},
+ { TEGRA210_AMX_RX4_CIF_CTRL, 0x00007000},
+ { TEGRA210_AMX_TX_INT_MASK, 0x00000001},
+ { TEGRA210_AMX_TX_CIF_CTRL, 0x00007000},
+ { TEGRA210_AMX_CG, 0x1},
+ { TEGRA210_AMX_CFG_RAM_CTRL, 0x00004000},
+};
+
+static void tegra210_amx_write_map_ram(struct tegra210_amx *amx)
+{
+ int i;
+
+ regmap_write(amx->regmap, TEGRA210_AMX_CFG_RAM_CTRL,
+ TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN |
+ TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN |
+ TEGRA210_AMX_CFG_RAM_CTRL_RW_WRITE);
+
+ for (i = 0; i < TEGRA210_AMX_RAM_DEPTH; i++)
+ regmap_write(amx->regmap, TEGRA210_AMX_CFG_RAM_DATA,
+ amx->map[i]);
+
+ regmap_write(amx->regmap, TEGRA210_AMX_OUT_BYTE_EN0, amx->byte_mask[0]);
+ regmap_write(amx->regmap, TEGRA210_AMX_OUT_BYTE_EN1, amx->byte_mask[1]);
+}
+
+static int tegra210_amx_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai);
+ unsigned int val;
+ int err;
+
+ /* Ensure if AMX is disabled */
+ err = regmap_read_poll_timeout(amx->regmap, TEGRA210_AMX_STATUS, val,
+ !(val & 0x1), 10, 10000);
+ if (err < 0) {
+ dev_err(dai->dev, "failed to stop AMX, err = %d\n", err);
+ return err;
+ }
+
+ /*
+ * Soft Reset: Below performs module soft reset which clears
+ * all FSM logic, flushes flow control of FIFO and resets the
+ * state register. It also brings module back to disabled
+ * state (without flushing the data in the pipe).
+ */
+ regmap_update_bits(amx->regmap, TEGRA210_AMX_SOFT_RESET,
+ TEGRA210_AMX_SOFT_RESET_SOFT_RESET_MASK,
+ TEGRA210_AMX_SOFT_RESET_SOFT_EN);
+
+ err = regmap_read_poll_timeout(amx->regmap, TEGRA210_AMX_SOFT_RESET,
+ val, !(val & 0x1), 10, 10000);
+ if (err < 0) {
+ dev_err(dai->dev, "failed to reset AMX, err = %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused tegra210_amx_runtime_suspend(struct device *dev)
+{
+ struct tegra210_amx *amx = dev_get_drvdata(dev);
+
+ regcache_cache_only(amx->regmap, true);
+ regcache_mark_dirty(amx->regmap);
+
+ return 0;
+}
+
+static int __maybe_unused tegra210_amx_runtime_resume(struct device *dev)
+{
+ struct tegra210_amx *amx = dev_get_drvdata(dev);
+
+ regcache_cache_only(amx->regmap, false);
+ regcache_sync(amx->regmap);
+
+ regmap_update_bits(amx->regmap,
+ TEGRA210_AMX_CTRL,
+ TEGRA210_AMX_CTRL_RX_DEP_MASK,
+ TEGRA210_AMX_WAIT_ON_ANY << TEGRA210_AMX_CTRL_RX_DEP_SHIFT);
+
+ tegra210_amx_write_map_ram(amx);
+
+ return 0;
+}
+
+static int tegra210_amx_set_audio_cif(struct snd_soc_dai *dai,
+ struct snd_pcm_hw_params *params,
+ unsigned int reg)
+{
+ struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai);
+ 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_S8:
+ audio_bits = TEGRA_ACIF_BITS_8;
+ break;
+ 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(amx->regmap, reg, &cif_conf);
+
+ return 0;
+}
+
+static int tegra210_amx_in_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai);
+
+ if (amx->soc_data->auto_disable) {
+ regmap_write(amx->regmap,
+ AMX_CH_REG(dai->id, TEGRA194_AMX_RX1_FRAME_PERIOD),
+ TEGRA194_MAX_FRAME_IDLE_COUNT);
+ regmap_write(amx->regmap, TEGRA210_AMX_CYA, 1);
+ }
+
+ return tegra210_amx_set_audio_cif(dai, params,
+ AMX_CH_REG(dai->id, TEGRA210_AMX_RX1_CIF_CTRL));
+}
+
+static int tegra210_amx_out_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ return tegra210_amx_set_audio_cif(dai, params,
+ TEGRA210_AMX_TX_CIF_CTRL);
+}
+
+static int tegra210_amx_get_byte_map(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct tegra210_amx *amx = snd_soc_component_get_drvdata(cmpnt);
+ unsigned char *bytes_map = (unsigned char *)&amx->map;
+ int reg = mc->reg;
+ int enabled;
+
+ if (reg > 31)
+ enabled = amx->byte_mask[1] & (1 << (reg - 32));
+ else
+ enabled = amx->byte_mask[0] & (1 << reg);
+
+ /*
+ * TODO: Simplify this logic to just return from bytes_map[]
+ *
+ * Presently below is required since bytes_map[] is
+ * tightly packed and cannot store the control value of 256.
+ * Byte mask state is used to know if 256 needs to be returned.
+ * Note that for control value of 256, the put() call stores 0
+ * in the bytes_map[] and disables the corresponding bit in
+ * byte_mask[].
+ */
+ if (enabled)
+ ucontrol->value.integer.value[0] = bytes_map[reg];
+ else
+ ucontrol->value.integer.value[0] = 256;
+
+ return 0;
+}
+
+static int tegra210_amx_put_byte_map(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_amx *amx = snd_soc_component_get_drvdata(cmpnt);
+ unsigned char *bytes_map = (unsigned char *)&amx->map;
+ int reg = mc->reg;
+ int value = ucontrol->value.integer.value[0];
+ unsigned int mask_val = amx->byte_mask[reg / 32];
+
+ if (value >= 0 && value <= 255)
+ mask_val |= (1 << (reg % 32));
+ else
+ mask_val &= ~(1 << (reg % 32));
+
+ if (mask_val == amx->byte_mask[reg / 32])
+ return 0;
+
+ /* Update byte map and slot */
+ bytes_map[reg] = value % 256;
+ amx->byte_mask[reg / 32] = mask_val;
+
+ return 1;
+}
+
+static const struct snd_soc_dai_ops tegra210_amx_out_dai_ops = {
+ .hw_params = tegra210_amx_out_hw_params,
+ .startup = tegra210_amx_startup,
+};
+
+static const struct snd_soc_dai_ops tegra210_amx_in_dai_ops = {
+ .hw_params = tegra210_amx_in_hw_params,
+};
+
+#define IN_DAI(id) \
+ { \
+ .name = "AMX-RX-CIF" #id, \
+ .playback = { \
+ .stream_name = "RX" #id "-CIF-Playback",\
+ .channels_min = 1, \
+ .channels_max = 16, \
+ .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" #id "-CIF-Capture", \
+ .channels_min = 1, \
+ .channels_max = 16, \
+ .rates = SNDRV_PCM_RATE_8000_192000, \
+ .formats = SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ }, \
+ .ops = &tegra210_amx_in_dai_ops, \
+ }
+
+#define OUT_DAI \
+ { \
+ .name = "AMX-TX-CIF", \
+ .playback = { \
+ .stream_name = "TX-CIF-Playback", \
+ .channels_min = 1, \
+ .channels_max = 16, \
+ .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 = 16, \
+ .rates = SNDRV_PCM_RATE_8000_192000, \
+ .formats = SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE, \
+ }, \
+ .ops = &tegra210_amx_out_dai_ops, \
+ }
+
+static struct snd_soc_dai_driver tegra210_amx_dais[] = {
+ IN_DAI(1),
+ IN_DAI(2),
+ IN_DAI(3),
+ IN_DAI(4),
+ OUT_DAI,
+};
+
+static const struct snd_soc_dapm_widget tegra210_amx_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("RX1", NULL, 0, TEGRA210_AMX_CTRL, 0, 0),
+ SND_SOC_DAPM_AIF_IN("RX2", NULL, 0, TEGRA210_AMX_CTRL, 1, 0),
+ SND_SOC_DAPM_AIF_IN("RX3", NULL, 0, TEGRA210_AMX_CTRL, 2, 0),
+ SND_SOC_DAPM_AIF_IN("RX4", NULL, 0, TEGRA210_AMX_CTRL, 3, 0),
+ SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_AMX_ENABLE,
+ TEGRA210_AMX_ENABLE_SHIFT, 0),
+};
+
+#define STREAM_ROUTES(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", NULL, "RX" #id }, \
+ { "TX-CIF-" sname, NULL, "TX" }, \
+ { "XBAR-" sname, NULL, "TX-CIF-" sname }, \
+ { "XBAR-RX", NULL, "XBAR-" sname }
+
+#define AMX_ROUTES(id) \
+ STREAM_ROUTES(id, "Playback"), \
+ STREAM_ROUTES(id, "Capture")
+
+static const struct snd_soc_dapm_route tegra210_amx_routes[] = {
+ AMX_ROUTES(1),
+ AMX_ROUTES(2),
+ AMX_ROUTES(3),
+ AMX_ROUTES(4),
+};
+
+#define TEGRA210_AMX_BYTE_MAP_CTRL(reg) \
+ SOC_SINGLE_EXT("Byte Map " #reg, reg, 0, 256, 0, \
+ tegra210_amx_get_byte_map, \
+ tegra210_amx_put_byte_map)
+
+static struct snd_kcontrol_new tegra210_amx_controls[] = {
+ TEGRA210_AMX_BYTE_MAP_CTRL(0),
+ TEGRA210_AMX_BYTE_MAP_CTRL(1),
+ TEGRA210_AMX_BYTE_MAP_CTRL(2),
+ TEGRA210_AMX_BYTE_MAP_CTRL(3),
+ TEGRA210_AMX_BYTE_MAP_CTRL(4),
+ TEGRA210_AMX_BYTE_MAP_CTRL(5),
+ TEGRA210_AMX_BYTE_MAP_CTRL(6),
+ TEGRA210_AMX_BYTE_MAP_CTRL(7),
+ TEGRA210_AMX_BYTE_MAP_CTRL(8),
+ TEGRA210_AMX_BYTE_MAP_CTRL(9),
+ TEGRA210_AMX_BYTE_MAP_CTRL(10),
+ TEGRA210_AMX_BYTE_MAP_CTRL(11),
+ TEGRA210_AMX_BYTE_MAP_CTRL(12),
+ TEGRA210_AMX_BYTE_MAP_CTRL(13),
+ TEGRA210_AMX_BYTE_MAP_CTRL(14),
+ TEGRA210_AMX_BYTE_MAP_CTRL(15),
+ TEGRA210_AMX_BYTE_MAP_CTRL(16),
+ TEGRA210_AMX_BYTE_MAP_CTRL(17),
+ TEGRA210_AMX_BYTE_MAP_CTRL(18),
+ TEGRA210_AMX_BYTE_MAP_CTRL(19),
+ TEGRA210_AMX_BYTE_MAP_CTRL(20),
+ TEGRA210_AMX_BYTE_MAP_CTRL(21),
+ TEGRA210_AMX_BYTE_MAP_CTRL(22),
+ TEGRA210_AMX_BYTE_MAP_CTRL(23),
+ TEGRA210_AMX_BYTE_MAP_CTRL(24),
+ TEGRA210_AMX_BYTE_MAP_CTRL(25),
+ TEGRA210_AMX_BYTE_MAP_CTRL(26),
+ TEGRA210_AMX_BYTE_MAP_CTRL(27),
+ TEGRA210_AMX_BYTE_MAP_CTRL(28),
+ TEGRA210_AMX_BYTE_MAP_CTRL(29),
+ TEGRA210_AMX_BYTE_MAP_CTRL(30),
+ TEGRA210_AMX_BYTE_MAP_CTRL(31),
+ TEGRA210_AMX_BYTE_MAP_CTRL(32),
+ TEGRA210_AMX_BYTE_MAP_CTRL(33),
+ TEGRA210_AMX_BYTE_MAP_CTRL(34),
+ TEGRA210_AMX_BYTE_MAP_CTRL(35),
+ TEGRA210_AMX_BYTE_MAP_CTRL(36),
+ TEGRA210_AMX_BYTE_MAP_CTRL(37),
+ TEGRA210_AMX_BYTE_MAP_CTRL(38),
+ TEGRA210_AMX_BYTE_MAP_CTRL(39),
+ TEGRA210_AMX_BYTE_MAP_CTRL(40),
+ TEGRA210_AMX_BYTE_MAP_CTRL(41),
+ TEGRA210_AMX_BYTE_MAP_CTRL(42),
+ TEGRA210_AMX_BYTE_MAP_CTRL(43),
+ TEGRA210_AMX_BYTE_MAP_CTRL(44),
+ TEGRA210_AMX_BYTE_MAP_CTRL(45),
+ TEGRA210_AMX_BYTE_MAP_CTRL(46),
+ TEGRA210_AMX_BYTE_MAP_CTRL(47),
+ TEGRA210_AMX_BYTE_MAP_CTRL(48),
+ TEGRA210_AMX_BYTE_MAP_CTRL(49),
+ TEGRA210_AMX_BYTE_MAP_CTRL(50),
+ TEGRA210_AMX_BYTE_MAP_CTRL(51),
+ TEGRA210_AMX_BYTE_MAP_CTRL(52),
+ TEGRA210_AMX_BYTE_MAP_CTRL(53),
+ TEGRA210_AMX_BYTE_MAP_CTRL(54),
+ TEGRA210_AMX_BYTE_MAP_CTRL(55),
+ TEGRA210_AMX_BYTE_MAP_CTRL(56),
+ TEGRA210_AMX_BYTE_MAP_CTRL(57),
+ TEGRA210_AMX_BYTE_MAP_CTRL(58),
+ TEGRA210_AMX_BYTE_MAP_CTRL(59),
+ TEGRA210_AMX_BYTE_MAP_CTRL(60),
+ TEGRA210_AMX_BYTE_MAP_CTRL(61),
+ TEGRA210_AMX_BYTE_MAP_CTRL(62),
+ TEGRA210_AMX_BYTE_MAP_CTRL(63),
+};
+
+static const struct snd_soc_component_driver tegra210_amx_cmpnt = {
+ .dapm_widgets = tegra210_amx_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra210_amx_widgets),
+ .dapm_routes = tegra210_amx_routes,
+ .num_dapm_routes = ARRAY_SIZE(tegra210_amx_routes),
+ .controls = tegra210_amx_controls,
+ .num_controls = ARRAY_SIZE(tegra210_amx_controls),
+};
+
+static bool tegra210_amx_wr_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_AMX_RX_INT_MASK ... TEGRA210_AMX_RX4_CIF_CTRL:
+ case TEGRA210_AMX_TX_INT_MASK ... TEGRA210_AMX_CG:
+ case TEGRA210_AMX_CTRL ... TEGRA210_AMX_CYA:
+ case TEGRA210_AMX_CFG_RAM_CTRL ... TEGRA210_AMX_CFG_RAM_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra194_amx_wr_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA194_AMX_RX1_FRAME_PERIOD ... TEGRA194_AMX_RX4_FRAME_PERIOD:
+ return true;
+ default:
+ return tegra210_amx_wr_reg(dev, reg);
+ }
+}
+
+static bool tegra210_amx_rd_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_AMX_RX_STATUS ... TEGRA210_AMX_CFG_RAM_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra194_amx_rd_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA194_AMX_RX1_FRAME_PERIOD ... TEGRA194_AMX_RX4_FRAME_PERIOD:
+ return true;
+ default:
+ return tegra210_amx_rd_reg(dev, reg);
+ }
+}
+
+static bool tegra210_amx_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_AMX_RX_STATUS:
+ case TEGRA210_AMX_RX_INT_STATUS:
+ case TEGRA210_AMX_RX_INT_SET:
+ case TEGRA210_AMX_TX_STATUS:
+ case TEGRA210_AMX_TX_INT_STATUS:
+ case TEGRA210_AMX_TX_INT_SET:
+ case TEGRA210_AMX_SOFT_RESET:
+ case TEGRA210_AMX_STATUS:
+ case TEGRA210_AMX_INT_STATUS:
+ case TEGRA210_AMX_CFG_RAM_CTRL:
+ case TEGRA210_AMX_CFG_RAM_DATA:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static const struct regmap_config tegra210_amx_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA210_AMX_CFG_RAM_DATA,
+ .writeable_reg = tegra210_amx_wr_reg,
+ .readable_reg = tegra210_amx_rd_reg,
+ .volatile_reg = tegra210_amx_volatile_reg,
+ .reg_defaults = tegra210_amx_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tegra210_amx_reg_defaults),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct regmap_config tegra194_amx_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA194_AMX_RX4_LAST_FRAME_PERIOD,
+ .writeable_reg = tegra194_amx_wr_reg,
+ .readable_reg = tegra194_amx_rd_reg,
+ .volatile_reg = tegra210_amx_volatile_reg,
+ .reg_defaults = tegra210_amx_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tegra210_amx_reg_defaults),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct tegra210_amx_soc_data soc_data_tegra210 = {
+ .regmap_conf = &tegra210_amx_regmap_config,
+};
+
+static const struct tegra210_amx_soc_data soc_data_tegra194 = {
+ .regmap_conf = &tegra194_amx_regmap_config,
+ .auto_disable = true,
+};
+
+static const struct of_device_id tegra210_amx_of_match[] = {
+ { .compatible = "nvidia,tegra210-amx", .data = &soc_data_tegra210 },
+ { .compatible = "nvidia,tegra194-amx", .data = &soc_data_tegra194 },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra210_amx_of_match);
+
+static int tegra210_amx_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tegra210_amx *amx;
+ void __iomem *regs;
+ int err;
+ const struct of_device_id *match;
+ struct tegra210_amx_soc_data *soc_data;
+
+ match = of_match_device(tegra210_amx_of_match, dev);
+
+ soc_data = (struct tegra210_amx_soc_data *)match->data;
+
+ amx = devm_kzalloc(dev, sizeof(*amx), GFP_KERNEL);
+ if (!amx)
+ return -ENOMEM;
+
+ amx->soc_data = soc_data;
+
+ dev_set_drvdata(dev, amx);
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ amx->regmap = devm_regmap_init_mmio(dev, regs,
+ soc_data->regmap_conf);
+ if (IS_ERR(amx->regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ return PTR_ERR(amx->regmap);
+ }
+
+ regcache_cache_only(amx->regmap, true);
+
+ err = devm_snd_soc_register_component(dev, &tegra210_amx_cmpnt,
+ tegra210_amx_dais,
+ ARRAY_SIZE(tegra210_amx_dais));
+ if (err) {
+ dev_err(dev, "can't register AMX component, err: %d\n", err);
+ return err;
+ }
+
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static int tegra210_amx_platform_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra210_amx_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra210_amx_runtime_suspend,
+ tegra210_amx_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver tegra210_amx_driver = {
+ .driver = {
+ .name = "tegra210-amx",
+ .of_match_table = tegra210_amx_of_match,
+ .pm = &tegra210_amx_pm_ops,
+ },
+ .probe = tegra210_amx_platform_probe,
+ .remove = tegra210_amx_platform_remove,
+};
+module_platform_driver(tegra210_amx_driver);
+
+MODULE_AUTHOR("Songhee Baek <sbaek@nvidia.com>");
+MODULE_DESCRIPTION("Tegra210 AMX ASoC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/tegra/tegra210_amx.h b/sound/soc/tegra/tegra210_amx.h
new file mode 100644
index 000000000..e277741e4
--- /dev/null
+++ b/sound/soc/tegra/tegra210_amx.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra210_amx.h - Definitions for Tegra210 AMX driver
+ *
+ * Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
+ *
+ */
+
+#ifndef __TEGRA210_AMX_H__
+#define __TEGRA210_AMX_H__
+
+/* Register offsets from TEGRA210_AMX*_BASE */
+#define TEGRA210_AMX_RX_STATUS 0x0c
+#define TEGRA210_AMX_RX_INT_STATUS 0x10
+#define TEGRA210_AMX_RX_INT_MASK 0x14
+#define TEGRA210_AMX_RX_INT_SET 0x18
+#define TEGRA210_AMX_RX_INT_CLEAR 0x1c
+#define TEGRA210_AMX_RX1_CIF_CTRL 0x20
+#define TEGRA210_AMX_RX2_CIF_CTRL 0x24
+#define TEGRA210_AMX_RX3_CIF_CTRL 0x28
+#define TEGRA210_AMX_RX4_CIF_CTRL 0x2c
+#define TEGRA210_AMX_TX_STATUS 0x4c
+#define TEGRA210_AMX_TX_INT_STATUS 0x50
+#define TEGRA210_AMX_TX_INT_MASK 0x54
+#define TEGRA210_AMX_TX_INT_SET 0x58
+#define TEGRA210_AMX_TX_INT_CLEAR 0x5c
+#define TEGRA210_AMX_TX_CIF_CTRL 0x60
+#define TEGRA210_AMX_ENABLE 0x80
+#define TEGRA210_AMX_SOFT_RESET 0x84
+#define TEGRA210_AMX_CG 0x88
+#define TEGRA210_AMX_STATUS 0x8c
+#define TEGRA210_AMX_INT_STATUS 0x90
+#define TEGRA210_AMX_CTRL 0xa4
+#define TEGRA210_AMX_OUT_BYTE_EN0 0xa8
+#define TEGRA210_AMX_OUT_BYTE_EN1 0xac
+#define TEGRA210_AMX_CYA 0xb0
+#define TEGRA210_AMX_CFG_RAM_CTRL 0xb8
+#define TEGRA210_AMX_CFG_RAM_DATA 0xbc
+
+#define TEGRA194_AMX_RX1_FRAME_PERIOD 0xc0
+#define TEGRA194_AMX_RX4_FRAME_PERIOD 0xcc
+#define TEGRA194_AMX_RX4_LAST_FRAME_PERIOD 0xdc
+
+/* Fields in TEGRA210_AMX_ENABLE */
+#define TEGRA210_AMX_ENABLE_SHIFT 0
+
+/* Fields in TEGRA210_AMX_CTRL */
+#define TEGRA210_AMX_CTRL_MSTR_RX_NUM_SHIFT 14
+#define TEGRA210_AMX_CTRL_MSTR_RX_NUM_MASK (3 << TEGRA210_AMX_CTRL_MSTR_RX_NUM_SHIFT)
+
+#define TEGRA210_AMX_CTRL_RX_DEP_SHIFT 12
+#define TEGRA210_AMX_CTRL_RX_DEP_MASK (3 << TEGRA210_AMX_CTRL_RX_DEP_SHIFT)
+
+/* Fields in TEGRA210_AMX_CFG_RAM_CTRL */
+#define TEGRA210_AMX_CFG_RAM_CTRL_RW_SHIFT 14
+#define TEGRA210_AMX_CFG_RAM_CTRL_RW_WRITE (1 << TEGRA210_AMX_CFG_RAM_CTRL_RW_SHIFT)
+
+#define TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT 13
+#define TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN (1 << TEGRA210_AMX_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT)
+
+#define TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT 12
+#define TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN (1 << TEGRA210_AMX_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT)
+
+#define TEGRA210_AMX_CFG_CTRL_RAM_ADDR_SHIFT 0
+
+/* Fields in TEGRA210_AMX_SOFT_RESET */
+#define TEGRA210_AMX_SOFT_RESET_SOFT_EN 1
+#define TEGRA210_AMX_SOFT_RESET_SOFT_RESET_MASK TEGRA210_AMX_SOFT_RESET_SOFT_EN
+
+#define TEGRA210_AMX_AUDIOCIF_CH_STRIDE 4
+#define TEGRA210_AMX_RAM_DEPTH 16
+#define TEGRA210_AMX_MAP_STREAM_NUM_SHIFT 6
+#define TEGRA210_AMX_MAP_WORD_NUM_SHIFT 2
+#define TEGRA210_AMX_MAP_BYTE_NUM_SHIFT 0
+
+enum {
+ TEGRA210_AMX_WAIT_ON_ALL,
+ TEGRA210_AMX_WAIT_ON_ANY,
+};
+
+struct tegra210_amx_soc_data {
+ const struct regmap_config *regmap_conf;
+ bool auto_disable;
+};
+
+struct tegra210_amx {
+ const struct tegra210_amx_soc_data *soc_data;
+ unsigned int map[TEGRA210_AMX_RAM_DEPTH];
+ struct regmap *regmap;
+ unsigned int byte_mask[2];
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra210_dmic.c b/sound/soc/tegra/tegra210_dmic.c
new file mode 100644
index 000000000..db9579453
--- /dev/null
+++ b/sound/soc/tegra/tegra210_dmic.c
@@ -0,0 +1,570 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// tegra210_dmic.c - Tegra210 DMIC driver
+//
+// Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved.
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/math64.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 <sound/core.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "tegra210_dmic.h"
+#include "tegra_cif.h"
+
+static const struct reg_default tegra210_dmic_reg_defaults[] = {
+ { TEGRA210_DMIC_TX_INT_MASK, 0x00000001 },
+ { TEGRA210_DMIC_TX_CIF_CTRL, 0x00007700 },
+ { TEGRA210_DMIC_CG, 0x1 },
+ { TEGRA210_DMIC_CTRL, 0x00000301 },
+ /* Below enables all filters - DCR, LP and SC */
+ { TEGRA210_DMIC_DBG_CTRL, 0xe },
+ /* Below as per latest POR value */
+ { TEGRA210_DMIC_DCR_BIQUAD_0_COEF_4, 0x0 },
+ /* LP filter is configured for pass through and used to apply gain */
+ { TEGRA210_DMIC_LP_BIQUAD_0_COEF_0, 0x00800000 },
+ { TEGRA210_DMIC_LP_BIQUAD_0_COEF_1, 0x0 },
+ { TEGRA210_DMIC_LP_BIQUAD_0_COEF_2, 0x0 },
+ { TEGRA210_DMIC_LP_BIQUAD_0_COEF_3, 0x0 },
+ { TEGRA210_DMIC_LP_BIQUAD_0_COEF_4, 0x0 },
+ { TEGRA210_DMIC_LP_BIQUAD_1_COEF_0, 0x00800000 },
+ { TEGRA210_DMIC_LP_BIQUAD_1_COEF_1, 0x0 },
+ { TEGRA210_DMIC_LP_BIQUAD_1_COEF_2, 0x0 },
+ { TEGRA210_DMIC_LP_BIQUAD_1_COEF_3, 0x0 },
+ { TEGRA210_DMIC_LP_BIQUAD_1_COEF_4, 0x0 },
+};
+
+static int __maybe_unused tegra210_dmic_runtime_suspend(struct device *dev)
+{
+ struct tegra210_dmic *dmic = dev_get_drvdata(dev);
+
+ regcache_cache_only(dmic->regmap, true);
+ regcache_mark_dirty(dmic->regmap);
+
+ clk_disable_unprepare(dmic->clk_dmic);
+
+ return 0;
+}
+
+static int __maybe_unused tegra210_dmic_runtime_resume(struct device *dev)
+{
+ struct tegra210_dmic *dmic = dev_get_drvdata(dev);
+ int err;
+
+ err = clk_prepare_enable(dmic->clk_dmic);
+ if (err) {
+ dev_err(dev, "failed to enable DMIC clock, err: %d\n", err);
+ return err;
+ }
+
+ regcache_cache_only(dmic->regmap, false);
+ regcache_sync(dmic->regmap);
+
+ return 0;
+}
+
+static int tegra210_dmic_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct tegra210_dmic *dmic = snd_soc_dai_get_drvdata(dai);
+ unsigned int srate, clk_rate, channels;
+ struct tegra_cif_conf cif_conf;
+ unsigned long long gain_q23 = DEFAULT_GAIN_Q23;
+ int err;
+
+ memset(&cif_conf, 0, sizeof(struct tegra_cif_conf));
+
+ channels = params_channels(params);
+
+ cif_conf.audio_ch = channels;
+
+ switch (dmic->ch_select) {
+ case DMIC_CH_SELECT_LEFT:
+ case DMIC_CH_SELECT_RIGHT:
+ cif_conf.client_ch = 1;
+ break;
+ case DMIC_CH_SELECT_STEREO:
+ cif_conf.client_ch = 2;
+ break;
+ default:
+ dev_err(dai->dev, "invalid DMIC client channels\n");
+ return -EINVAL;
+ }
+
+ srate = params_rate(params);
+
+ /*
+ * DMIC clock rate is a multiple of 'Over Sampling Ratio' and
+ * 'Sample Rate'. The supported OSR values are 64, 128 and 256.
+ */
+ clk_rate = (DMIC_OSR_FACTOR << dmic->osr_val) * srate;
+
+ err = clk_set_rate(dmic->clk_dmic, clk_rate);
+ if (err) {
+ dev_err(dai->dev, "can't set DMIC clock rate %u, err: %d\n",
+ clk_rate, err);
+ return err;
+ }
+
+ regmap_update_bits(dmic->regmap,
+ /* Reg */
+ TEGRA210_DMIC_CTRL,
+ /* Mask */
+ TEGRA210_DMIC_CTRL_LRSEL_POLARITY_MASK |
+ TEGRA210_DMIC_CTRL_OSR_MASK |
+ TEGRA210_DMIC_CTRL_CHANNEL_SELECT_MASK,
+ /* Value */
+ (dmic->lrsel << LRSEL_POL_SHIFT) |
+ (dmic->osr_val << OSR_SHIFT) |
+ ((dmic->ch_select + 1) << CH_SEL_SHIFT));
+
+ /*
+ * Use LP filter gain register to apply boost.
+ * Boost Gain Volume control has 100x factor.
+ */
+ if (dmic->boost_gain)
+ gain_q23 = div_u64(gain_q23 * dmic->boost_gain, 100);
+
+ regmap_write(dmic->regmap, TEGRA210_DMIC_LP_FILTER_GAIN,
+ (unsigned int)gain_q23);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ cif_conf.audio_bits = TEGRA_ACIF_BITS_16;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ cif_conf.audio_bits = TEGRA_ACIF_BITS_32;
+ break;
+ default:
+ dev_err(dai->dev, "unsupported format!\n");
+ return -EOPNOTSUPP;
+ }
+
+ cif_conf.client_bits = TEGRA_ACIF_BITS_24;
+ cif_conf.mono_conv = dmic->mono_to_stereo;
+ cif_conf.stereo_conv = dmic->stereo_to_mono;
+
+ tegra_set_cif(dmic->regmap, TEGRA210_DMIC_TX_CIF_CTRL, &cif_conf);
+
+ return 0;
+}
+
+static int tegra210_dmic_get_boost_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.integer.value[0] = dmic->boost_gain;
+
+ return 0;
+}
+
+static int tegra210_dmic_put_boost_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+ int value = ucontrol->value.integer.value[0];
+
+ if (value == dmic->boost_gain)
+ return 0;
+
+ dmic->boost_gain = value;
+
+ return 1;
+}
+
+static int tegra210_dmic_get_ch_select(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = dmic->ch_select;
+
+ return 0;
+}
+
+static int tegra210_dmic_put_ch_select(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == dmic->ch_select)
+ return 0;
+
+ dmic->ch_select = value;
+
+ return 1;
+}
+
+static int tegra210_dmic_get_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = dmic->mono_to_stereo;
+
+ return 0;
+}
+
+static int tegra210_dmic_put_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == dmic->mono_to_stereo)
+ return 0;
+
+ dmic->mono_to_stereo = value;
+
+ return 1;
+}
+
+static int tegra210_dmic_get_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = dmic->stereo_to_mono;
+
+ return 0;
+}
+
+static int tegra210_dmic_put_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == dmic->stereo_to_mono)
+ return 0;
+
+ dmic->stereo_to_mono = value;
+
+ return 1;
+}
+
+static int tegra210_dmic_get_osr_val(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = dmic->osr_val;
+
+ return 0;
+}
+
+static int tegra210_dmic_put_osr_val(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == dmic->osr_val)
+ return 0;
+
+ dmic->osr_val = value;
+
+ return 1;
+}
+
+static int tegra210_dmic_get_pol_sel(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = dmic->lrsel;
+
+ return 0;
+}
+
+static int tegra210_dmic_put_pol_sel(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_dmic *dmic = snd_soc_component_get_drvdata(comp);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == dmic->lrsel)
+ return 0;
+
+ dmic->lrsel = value;
+
+ return 1;
+}
+
+static const struct snd_soc_dai_ops tegra210_dmic_dai_ops = {
+ .hw_params = tegra210_dmic_hw_params,
+};
+
+static struct snd_soc_dai_driver tegra210_dmic_dais[] = {
+ {
+ .name = "DMIC-CIF",
+ .capture = {
+ .stream_name = "CIF-Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ },
+ {
+ .name = "DMIC-DAP",
+ .capture = {
+ .stream_name = "DAP-Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &tegra210_dmic_dai_ops,
+ .symmetric_rate = 1,
+ },
+};
+
+static const struct snd_soc_dapm_widget tegra210_dmic_widgets[] = {
+ SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_DMIC_ENABLE, 0, 0),
+ SND_SOC_DAPM_MIC("MIC", NULL),
+};
+
+static const struct snd_soc_dapm_route tegra210_dmic_routes[] = {
+ { "XBAR-RX", NULL, "XBAR-Capture" },
+ { "XBAR-Capture", NULL, "CIF-Capture" },
+ { "CIF-Capture", NULL, "TX" },
+ { "TX", NULL, "DAP-Capture" },
+ { "DAP-Capture", NULL, "MIC" },
+};
+
+static const char * const tegra210_dmic_ch_select[] = {
+ "Left", "Right", "Stereo",
+};
+
+static const struct soc_enum tegra210_dmic_ch_enum =
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_ch_select),
+ tegra210_dmic_ch_select);
+
+static const char * const tegra210_dmic_mono_conv_text[] = {
+ "Zero", "Copy",
+};
+
+static const char * const tegra210_dmic_stereo_conv_text[] = {
+ "CH0", "CH1", "AVG",
+};
+
+static const struct soc_enum tegra210_dmic_mono_conv_enum =
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_mono_conv_text),
+ tegra210_dmic_mono_conv_text);
+
+static const struct soc_enum tegra210_dmic_stereo_conv_enum =
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_stereo_conv_text),
+ tegra210_dmic_stereo_conv_text);
+
+static const char * const tegra210_dmic_osr_text[] = {
+ "OSR_64", "OSR_128", "OSR_256",
+};
+
+static const struct soc_enum tegra210_dmic_osr_enum =
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_osr_text),
+ tegra210_dmic_osr_text);
+
+static const char * const tegra210_dmic_lrsel_text[] = {
+ "Left", "Right",
+};
+
+static const struct soc_enum tegra210_dmic_lrsel_enum =
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_dmic_lrsel_text),
+ tegra210_dmic_lrsel_text);
+
+static const struct snd_kcontrol_new tegra210_dmic_controls[] = {
+ SOC_SINGLE_EXT("Boost Gain Volume", 0, 0, MAX_BOOST_GAIN, 0,
+ tegra210_dmic_get_boost_gain,
+ tegra210_dmic_put_boost_gain),
+ SOC_ENUM_EXT("Channel Select", tegra210_dmic_ch_enum,
+ tegra210_dmic_get_ch_select, tegra210_dmic_put_ch_select),
+ SOC_ENUM_EXT("Mono To Stereo",
+ tegra210_dmic_mono_conv_enum,
+ tegra210_dmic_get_mono_to_stereo,
+ tegra210_dmic_put_mono_to_stereo),
+ SOC_ENUM_EXT("Stereo To Mono",
+ tegra210_dmic_stereo_conv_enum,
+ tegra210_dmic_get_stereo_to_mono,
+ tegra210_dmic_put_stereo_to_mono),
+ SOC_ENUM_EXT("OSR Value", tegra210_dmic_osr_enum,
+ tegra210_dmic_get_osr_val, tegra210_dmic_put_osr_val),
+ SOC_ENUM_EXT("LR Polarity Select", tegra210_dmic_lrsel_enum,
+ tegra210_dmic_get_pol_sel, tegra210_dmic_put_pol_sel),
+};
+
+static const struct snd_soc_component_driver tegra210_dmic_compnt = {
+ .dapm_widgets = tegra210_dmic_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra210_dmic_widgets),
+ .dapm_routes = tegra210_dmic_routes,
+ .num_dapm_routes = ARRAY_SIZE(tegra210_dmic_routes),
+ .controls = tegra210_dmic_controls,
+ .num_controls = ARRAY_SIZE(tegra210_dmic_controls),
+};
+
+static bool tegra210_dmic_wr_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_DMIC_TX_INT_MASK ... TEGRA210_DMIC_TX_CIF_CTRL:
+ case TEGRA210_DMIC_ENABLE ... TEGRA210_DMIC_CG:
+ case TEGRA210_DMIC_CTRL:
+ case TEGRA210_DMIC_DBG_CTRL:
+ case TEGRA210_DMIC_DCR_BIQUAD_0_COEF_4 ... TEGRA210_DMIC_LP_BIQUAD_1_COEF_4:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_dmic_rd_reg(struct device *dev, unsigned int reg)
+{
+ if (tegra210_dmic_wr_reg(dev, reg))
+ return true;
+
+ switch (reg) {
+ case TEGRA210_DMIC_TX_STATUS:
+ case TEGRA210_DMIC_TX_INT_STATUS:
+ case TEGRA210_DMIC_STATUS:
+ case TEGRA210_DMIC_INT_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_dmic_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_DMIC_TX_STATUS:
+ case TEGRA210_DMIC_TX_INT_STATUS:
+ case TEGRA210_DMIC_TX_INT_SET:
+ case TEGRA210_DMIC_SOFT_RESET:
+ case TEGRA210_DMIC_STATUS:
+ case TEGRA210_DMIC_INT_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tegra210_dmic_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA210_DMIC_LP_BIQUAD_1_COEF_4,
+ .writeable_reg = tegra210_dmic_wr_reg,
+ .readable_reg = tegra210_dmic_rd_reg,
+ .volatile_reg = tegra210_dmic_volatile_reg,
+ .reg_defaults = tegra210_dmic_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tegra210_dmic_reg_defaults),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int tegra210_dmic_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tegra210_dmic *dmic;
+ void __iomem *regs;
+ int err;
+
+ dmic = devm_kzalloc(dev, sizeof(*dmic), GFP_KERNEL);
+ if (!dmic)
+ return -ENOMEM;
+
+ dmic->osr_val = DMIC_OSR_64;
+ dmic->ch_select = DMIC_CH_SELECT_STEREO;
+ dmic->lrsel = DMIC_LRSEL_LEFT;
+ dmic->boost_gain = 0;
+ dmic->stereo_to_mono = 0; /* "CH0" */
+
+ dev_set_drvdata(dev, dmic);
+
+ dmic->clk_dmic = devm_clk_get(dev, "dmic");
+ if (IS_ERR(dmic->clk_dmic)) {
+ dev_err(dev, "can't retrieve DMIC clock\n");
+ return PTR_ERR(dmic->clk_dmic);
+ }
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ dmic->regmap = devm_regmap_init_mmio(dev, regs,
+ &tegra210_dmic_regmap_config);
+ if (IS_ERR(dmic->regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ return PTR_ERR(dmic->regmap);
+ }
+
+ regcache_cache_only(dmic->regmap, true);
+
+ err = devm_snd_soc_register_component(dev, &tegra210_dmic_compnt,
+ tegra210_dmic_dais,
+ ARRAY_SIZE(tegra210_dmic_dais));
+ if (err) {
+ dev_err(dev, "can't register DMIC component, err: %d\n", err);
+ return err;
+ }
+
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static int tegra210_dmic_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra210_dmic_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra210_dmic_runtime_suspend,
+ tegra210_dmic_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static const struct of_device_id tegra210_dmic_of_match[] = {
+ { .compatible = "nvidia,tegra210-dmic" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra210_dmic_of_match);
+
+static struct platform_driver tegra210_dmic_driver = {
+ .driver = {
+ .name = "tegra210-dmic",
+ .of_match_table = tegra210_dmic_of_match,
+ .pm = &tegra210_dmic_pm_ops,
+ },
+ .probe = tegra210_dmic_probe,
+ .remove = tegra210_dmic_remove,
+};
+module_platform_driver(tegra210_dmic_driver)
+
+MODULE_AUTHOR("Rahul Mittal <rmittal@nvidia.com>");
+MODULE_DESCRIPTION("Tegra210 ASoC DMIC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/tegra/tegra210_dmic.h b/sound/soc/tegra/tegra210_dmic.h
new file mode 100644
index 000000000..6418c223b
--- /dev/null
+++ b/sound/soc/tegra/tegra210_dmic.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra210_dmic.h - Definitions for Tegra210 DMIC driver
+ *
+ * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved.
+ *
+ */
+
+#ifndef __TEGRA210_DMIC_H__
+#define __TEGRA210_DMIC_H__
+
+/* Register offsets from DMIC BASE */
+#define TEGRA210_DMIC_TX_STATUS 0x0c
+#define TEGRA210_DMIC_TX_INT_STATUS 0x10
+#define TEGRA210_DMIC_TX_INT_MASK 0x14
+#define TEGRA210_DMIC_TX_INT_SET 0x18
+#define TEGRA210_DMIC_TX_INT_CLEAR 0x1c
+#define TEGRA210_DMIC_TX_CIF_CTRL 0x20
+#define TEGRA210_DMIC_ENABLE 0x40
+#define TEGRA210_DMIC_SOFT_RESET 0x44
+#define TEGRA210_DMIC_CG 0x48
+#define TEGRA210_DMIC_STATUS 0x4c
+#define TEGRA210_DMIC_INT_STATUS 0x50
+#define TEGRA210_DMIC_CTRL 0x64
+#define TEGRA210_DMIC_DBG_CTRL 0x70
+#define TEGRA210_DMIC_DCR_BIQUAD_0_COEF_4 0x88
+#define TEGRA210_DMIC_LP_FILTER_GAIN 0x8c
+#define TEGRA210_DMIC_LP_BIQUAD_0_COEF_0 0x90
+#define TEGRA210_DMIC_LP_BIQUAD_0_COEF_1 0x94
+#define TEGRA210_DMIC_LP_BIQUAD_0_COEF_2 0x98
+#define TEGRA210_DMIC_LP_BIQUAD_0_COEF_3 0x9c
+#define TEGRA210_DMIC_LP_BIQUAD_0_COEF_4 0xa0
+#define TEGRA210_DMIC_LP_BIQUAD_1_COEF_0 0xa4
+#define TEGRA210_DMIC_LP_BIQUAD_1_COEF_1 0xa8
+#define TEGRA210_DMIC_LP_BIQUAD_1_COEF_2 0xac
+#define TEGRA210_DMIC_LP_BIQUAD_1_COEF_3 0xb0
+#define TEGRA210_DMIC_LP_BIQUAD_1_COEF_4 0xb4
+
+/* Fields in TEGRA210_DMIC_CTRL */
+#define CH_SEL_SHIFT 8
+#define TEGRA210_DMIC_CTRL_CHANNEL_SELECT_MASK (0x3 << CH_SEL_SHIFT)
+#define LRSEL_POL_SHIFT 4
+#define TEGRA210_DMIC_CTRL_LRSEL_POLARITY_MASK (0x1 << LRSEL_POL_SHIFT)
+#define OSR_SHIFT 0
+#define TEGRA210_DMIC_CTRL_OSR_MASK (0x3 << OSR_SHIFT)
+
+#define DMIC_OSR_FACTOR 64
+
+#define DEFAULT_GAIN_Q23 0x800000
+
+/* Max boost gain factor used for mixer control */
+#define MAX_BOOST_GAIN 25599
+
+enum tegra_dmic_ch_select {
+ DMIC_CH_SELECT_LEFT,
+ DMIC_CH_SELECT_RIGHT,
+ DMIC_CH_SELECT_STEREO,
+};
+
+enum tegra_dmic_osr {
+ DMIC_OSR_64,
+ DMIC_OSR_128,
+ DMIC_OSR_256,
+};
+
+enum tegra_dmic_lrsel {
+ DMIC_LRSEL_LEFT,
+ DMIC_LRSEL_RIGHT,
+};
+
+struct tegra210_dmic {
+ struct clk *clk_dmic;
+ struct regmap *regmap;
+ unsigned int mono_to_stereo;
+ unsigned int stereo_to_mono;
+ unsigned int boost_gain;
+ unsigned int ch_select;
+ unsigned int osr_val;
+ unsigned int lrsel;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c
new file mode 100644
index 000000000..39ffa4d76
--- /dev/null
+++ b/sound/soc/tegra/tegra210_i2s.c
@@ -0,0 +1,967 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// tegra210_i2s.c - Tegra210 I2S driver
+//
+// Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved.
+
+#include <linux/clk.h>
+#include <linux/device.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 <sound/core.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "tegra210_i2s.h"
+#include "tegra_cif.h"
+
+static const struct reg_default tegra210_i2s_reg_defaults[] = {
+ { TEGRA210_I2S_RX_INT_MASK, 0x00000003 },
+ { TEGRA210_I2S_RX_CIF_CTRL, 0x00007700 },
+ { TEGRA210_I2S_TX_INT_MASK, 0x00000003 },
+ { TEGRA210_I2S_TX_CIF_CTRL, 0x00007700 },
+ { TEGRA210_I2S_CG, 0x1 },
+ { TEGRA210_I2S_TIMING, 0x0000001f },
+ { TEGRA210_I2S_ENABLE, 0x1 },
+ /*
+ * Below update does not have any effect on Tegra186 and Tegra194.
+ * On Tegra210, I2S4 has "i2s4a" and "i2s4b" pins and below update
+ * is required to select i2s4b for it to be functional for I2S
+ * operation.
+ */
+ { TEGRA210_I2S_CYA, 0x1 },
+};
+
+static void tegra210_i2s_set_slot_ctrl(struct regmap *regmap,
+ unsigned int total_slots,
+ unsigned int tx_slot_mask,
+ unsigned int rx_slot_mask)
+{
+ regmap_write(regmap, TEGRA210_I2S_SLOT_CTRL, total_slots - 1);
+ regmap_write(regmap, TEGRA210_I2S_TX_SLOT_CTRL, tx_slot_mask);
+ regmap_write(regmap, TEGRA210_I2S_RX_SLOT_CTRL, rx_slot_mask);
+}
+
+static int tegra210_i2s_set_clock_rate(struct device *dev,
+ unsigned int clock_rate)
+{
+ struct tegra210_i2s *i2s = dev_get_drvdata(dev);
+ unsigned int val;
+ int err;
+
+ regmap_read(i2s->regmap, TEGRA210_I2S_CTRL, &val);
+
+ /* No need to set rates if I2S is being operated in slave */
+ if (!(val & I2S_CTRL_MASTER_EN))
+ return 0;
+
+ err = clk_set_rate(i2s->clk_i2s, clock_rate);
+ if (err) {
+ dev_err(dev, "can't set I2S bit clock rate %u, err: %d\n",
+ clock_rate, err);
+ return err;
+ }
+
+ if (!IS_ERR(i2s->clk_sync_input)) {
+ /*
+ * Other I/O modules in AHUB can use i2s bclk as reference
+ * clock. Below sets sync input clock rate as per bclk,
+ * which can be used as input to other I/O modules.
+ */
+ err = clk_set_rate(i2s->clk_sync_input, clock_rate);
+ if (err) {
+ dev_err(dev,
+ "can't set I2S sync input rate %u, err = %d\n",
+ clock_rate, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int tegra210_i2s_sw_reset(struct snd_soc_component *compnt,
+ bool is_playback)
+{
+ struct device *dev = compnt->dev;
+ struct tegra210_i2s *i2s = dev_get_drvdata(dev);
+ unsigned int reset_mask = I2S_SOFT_RESET_MASK;
+ unsigned int reset_en = I2S_SOFT_RESET_EN;
+ unsigned int reset_reg, cif_reg, stream_reg;
+ unsigned int cif_ctrl, stream_ctrl, i2s_ctrl, val;
+ int err;
+
+ if (is_playback) {
+ reset_reg = TEGRA210_I2S_RX_SOFT_RESET;
+ cif_reg = TEGRA210_I2S_RX_CIF_CTRL;
+ stream_reg = TEGRA210_I2S_RX_CTRL;
+ } else {
+ reset_reg = TEGRA210_I2S_TX_SOFT_RESET;
+ cif_reg = TEGRA210_I2S_TX_CIF_CTRL;
+ stream_reg = TEGRA210_I2S_TX_CTRL;
+ }
+
+ /* Store CIF and I2S control values */
+ regmap_read(i2s->regmap, cif_reg, &cif_ctrl);
+ regmap_read(i2s->regmap, stream_reg, &stream_ctrl);
+ regmap_read(i2s->regmap, TEGRA210_I2S_CTRL, &i2s_ctrl);
+
+ /* Reset to make sure the previous transactions are clean */
+ regmap_update_bits(i2s->regmap, reset_reg, reset_mask, reset_en);
+
+ err = regmap_read_poll_timeout(i2s->regmap, reset_reg, val,
+ !(val & reset_mask & reset_en),
+ 10, 10000);
+ if (err) {
+ dev_err(dev, "timeout: failed to reset I2S for %s\n",
+ is_playback ? "playback" : "capture");
+ return err;
+ }
+
+ /* Restore CIF and I2S control values */
+ regmap_write(i2s->regmap, cif_reg, cif_ctrl);
+ regmap_write(i2s->regmap, stream_reg, stream_ctrl);
+ regmap_write(i2s->regmap, TEGRA210_I2S_CTRL, i2s_ctrl);
+
+ return 0;
+}
+
+static int tegra210_i2s_init(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *compnt = snd_soc_dapm_to_component(w->dapm);
+ struct device *dev = compnt->dev;
+ struct tegra210_i2s *i2s = dev_get_drvdata(dev);
+ unsigned int val, status_reg;
+ bool is_playback;
+ int err;
+
+ switch (w->reg) {
+ case TEGRA210_I2S_RX_ENABLE:
+ is_playback = true;
+ status_reg = TEGRA210_I2S_RX_STATUS;
+ break;
+ case TEGRA210_I2S_TX_ENABLE:
+ is_playback = false;
+ status_reg = TEGRA210_I2S_TX_STATUS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Ensure I2S is in disabled state before new session */
+ err = regmap_read_poll_timeout(i2s->regmap, status_reg, val,
+ !(val & I2S_EN_MASK & I2S_EN),
+ 10, 10000);
+ if (err) {
+ dev_err(dev, "timeout: previous I2S %s is still active\n",
+ is_playback ? "playback" : "capture");
+ return err;
+ }
+
+ return tegra210_i2s_sw_reset(compnt, is_playback);
+}
+
+static int __maybe_unused tegra210_i2s_runtime_suspend(struct device *dev)
+{
+ struct tegra210_i2s *i2s = dev_get_drvdata(dev);
+
+ regcache_cache_only(i2s->regmap, true);
+ regcache_mark_dirty(i2s->regmap);
+
+ clk_disable_unprepare(i2s->clk_i2s);
+
+ return 0;
+}
+
+static int __maybe_unused tegra210_i2s_runtime_resume(struct device *dev)
+{
+ struct tegra210_i2s *i2s = dev_get_drvdata(dev);
+ int err;
+
+ err = clk_prepare_enable(i2s->clk_i2s);
+ if (err) {
+ dev_err(dev, "failed to enable I2S bit clock, err: %d\n", err);
+ return err;
+ }
+
+ regcache_cache_only(i2s->regmap, false);
+ regcache_sync(i2s->regmap);
+
+ return 0;
+}
+
+static void tegra210_i2s_set_data_offset(struct tegra210_i2s *i2s,
+ unsigned int data_offset)
+{
+ /* Capture path */
+ regmap_update_bits(i2s->regmap, TEGRA210_I2S_TX_CTRL,
+ I2S_CTRL_DATA_OFFSET_MASK,
+ data_offset << I2S_DATA_SHIFT);
+
+ /* Playback path */
+ regmap_update_bits(i2s->regmap, TEGRA210_I2S_RX_CTRL,
+ I2S_CTRL_DATA_OFFSET_MASK,
+ data_offset << I2S_DATA_SHIFT);
+}
+
+static int tegra210_i2s_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct tegra210_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ unsigned int mask, val;
+
+ mask = I2S_CTRL_MASTER_EN_MASK;
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FC:
+ val = 0;
+ break;
+ case SND_SOC_DAIFMT_BP_FP:
+ val = I2S_CTRL_MASTER_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mask |= I2S_CTRL_FRAME_FMT_MASK | I2S_CTRL_LRCK_POL_MASK;
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ val |= I2S_CTRL_FRAME_FMT_FSYNC_MODE;
+ val |= I2S_CTRL_LRCK_POL_HIGH;
+ tegra210_i2s_set_data_offset(i2s, 1);
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ val |= I2S_CTRL_FRAME_FMT_FSYNC_MODE;
+ val |= I2S_CTRL_LRCK_POL_HIGH;
+ tegra210_i2s_set_data_offset(i2s, 0);
+ break;
+ /* I2S mode has data offset of 1 */
+ case SND_SOC_DAIFMT_I2S:
+ val |= I2S_CTRL_FRAME_FMT_LRCK_MODE;
+ val |= I2S_CTRL_LRCK_POL_LOW;
+ tegra210_i2s_set_data_offset(i2s, 1);
+ break;
+ /*
+ * For RJ mode data offset is dependent on the sample size
+ * and the bclk ratio, and so is set when hw_params is called.
+ */
+ case SND_SOC_DAIFMT_RIGHT_J:
+ val |= I2S_CTRL_FRAME_FMT_LRCK_MODE;
+ val |= I2S_CTRL_LRCK_POL_HIGH;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ val |= I2S_CTRL_FRAME_FMT_LRCK_MODE;
+ val |= I2S_CTRL_LRCK_POL_HIGH;
+ tegra210_i2s_set_data_offset(i2s, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mask |= I2S_CTRL_EDGE_CTRL_MASK;
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ val |= I2S_CTRL_EDGE_CTRL_POS_EDGE;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ val |= I2S_CTRL_EDGE_CTRL_POS_EDGE;
+ val ^= I2S_CTRL_LRCK_POL_MASK;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ val |= I2S_CTRL_EDGE_CTRL_NEG_EDGE;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ val |= I2S_CTRL_EDGE_CTRL_NEG_EDGE;
+ val ^= I2S_CTRL_LRCK_POL_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL, mask, val);
+
+ i2s->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+
+ return 0;
+}
+
+static int tegra210_i2s_set_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int slots, int slot_width)
+{
+ struct tegra210_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ /* Copy the required tx and rx mask */
+ i2s->tx_mask = (tx_mask > DEFAULT_I2S_SLOT_MASK) ?
+ DEFAULT_I2S_SLOT_MASK : tx_mask;
+ i2s->rx_mask = (rx_mask > DEFAULT_I2S_SLOT_MASK) ?
+ DEFAULT_I2S_SLOT_MASK : rx_mask;
+
+ return 0;
+}
+
+static int tegra210_i2s_get_loopback(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+
+ ucontrol->value.integer.value[0] = i2s->loopback;
+
+ return 0;
+}
+
+static int tegra210_i2s_put_loopback(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+ int value = ucontrol->value.integer.value[0];
+
+ if (value == i2s->loopback)
+ return 0;
+
+ i2s->loopback = value;
+
+ regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL, I2S_CTRL_LPBK_MASK,
+ i2s->loopback << I2S_CTRL_LPBK_SHIFT);
+
+ return 1;
+}
+
+static int tegra210_i2s_get_fsync_width(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+
+ ucontrol->value.integer.value[0] = i2s->fsync_width;
+
+ return 0;
+}
+
+static int tegra210_i2s_put_fsync_width(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+ int value = ucontrol->value.integer.value[0];
+
+ if (value == i2s->fsync_width)
+ return 0;
+
+ i2s->fsync_width = value;
+
+ /*
+ * Frame sync width is used only for FSYNC modes and not
+ * applicable for LRCK modes. Reset value for this field is "0",
+ * which means the width is one bit clock wide.
+ * The width requirement may depend on the codec and in such
+ * cases mixer control is used to update custom values. A value
+ * of "N" here means, width is "N + 1" bit clock wide.
+ */
+ regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL,
+ I2S_CTRL_FSYNC_WIDTH_MASK,
+ i2s->fsync_width << I2S_FSYNC_WIDTH_SHIFT);
+
+ return 1;
+}
+
+static int tegra210_i2s_cget_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+
+ ucontrol->value.enumerated.item[0] = i2s->stereo_to_mono[I2S_TX_PATH];
+
+ return 0;
+}
+
+static int tegra210_i2s_cput_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == i2s->stereo_to_mono[I2S_TX_PATH])
+ return 0;
+
+ i2s->stereo_to_mono[I2S_TX_PATH] = value;
+
+ return 1;
+}
+
+static int tegra210_i2s_cget_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+
+ ucontrol->value.enumerated.item[0] = i2s->mono_to_stereo[I2S_TX_PATH];
+
+ return 0;
+}
+
+static int tegra210_i2s_cput_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == i2s->mono_to_stereo[I2S_TX_PATH])
+ return 0;
+
+ i2s->mono_to_stereo[I2S_TX_PATH] = value;
+
+ return 1;
+}
+
+static int tegra210_i2s_pget_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+
+ ucontrol->value.enumerated.item[0] = i2s->stereo_to_mono[I2S_RX_PATH];
+
+ return 0;
+}
+
+static int tegra210_i2s_pput_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == i2s->stereo_to_mono[I2S_RX_PATH])
+ return 0;
+
+ i2s->stereo_to_mono[I2S_RX_PATH] = value;
+
+ return 1;
+}
+
+static int tegra210_i2s_pget_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+
+ ucontrol->value.enumerated.item[0] = i2s->mono_to_stereo[I2S_RX_PATH];
+
+ return 0;
+}
+
+static int tegra210_i2s_pput_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == i2s->mono_to_stereo[I2S_RX_PATH])
+ return 0;
+
+ i2s->mono_to_stereo[I2S_RX_PATH] = value;
+
+ return 1;
+}
+
+static int tegra210_i2s_pget_fifo_th(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+
+ ucontrol->value.integer.value[0] = i2s->rx_fifo_th;
+
+ return 0;
+}
+
+static int tegra210_i2s_pput_fifo_th(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+ int value = ucontrol->value.integer.value[0];
+
+ if (value == i2s->rx_fifo_th)
+ return 0;
+
+ i2s->rx_fifo_th = value;
+
+ return 1;
+}
+
+static int tegra210_i2s_get_bclk_ratio(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+
+ ucontrol->value.integer.value[0] = i2s->bclk_ratio;
+
+ return 0;
+}
+
+static int tegra210_i2s_put_bclk_ratio(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *compnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_i2s *i2s = snd_soc_component_get_drvdata(compnt);
+ int value = ucontrol->value.integer.value[0];
+
+ if (value == i2s->bclk_ratio)
+ return 0;
+
+ i2s->bclk_ratio = value;
+
+ return 1;
+}
+
+static int tegra210_i2s_set_dai_bclk_ratio(struct snd_soc_dai *dai,
+ unsigned int ratio)
+{
+ struct tegra210_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ i2s->bclk_ratio = ratio;
+
+ return 0;
+}
+
+static int tegra210_i2s_set_timing_params(struct device *dev,
+ unsigned int sample_size,
+ unsigned int srate,
+ unsigned int channels)
+{
+ struct tegra210_i2s *i2s = dev_get_drvdata(dev);
+ unsigned int val, bit_count, bclk_rate, num_bclk = sample_size;
+ int err;
+
+ if (i2s->bclk_ratio)
+ num_bclk *= i2s->bclk_ratio;
+
+ if (i2s->dai_fmt == SND_SOC_DAIFMT_RIGHT_J)
+ tegra210_i2s_set_data_offset(i2s, num_bclk - sample_size);
+
+ /* I2S bit clock rate */
+ bclk_rate = srate * channels * num_bclk;
+
+ err = tegra210_i2s_set_clock_rate(dev, bclk_rate);
+ if (err) {
+ dev_err(dev, "can't set I2S bit clock rate %u, err: %d\n",
+ bclk_rate, err);
+ return err;
+ }
+
+ regmap_read(i2s->regmap, TEGRA210_I2S_CTRL, &val);
+
+ /*
+ * For LRCK mode, channel bit count depends on number of bit clocks
+ * on the left channel, where as for FSYNC mode bit count depends on
+ * the number of bit clocks in both left and right channels for DSP
+ * mode or the number of bit clocks in one TDM frame.
+ *
+ */
+ switch (val & I2S_CTRL_FRAME_FMT_MASK) {
+ case I2S_CTRL_FRAME_FMT_LRCK_MODE:
+ bit_count = (bclk_rate / (srate * 2)) - 1;
+ break;
+ case I2S_CTRL_FRAME_FMT_FSYNC_MODE:
+ bit_count = (bclk_rate / srate) - 1;
+
+ tegra210_i2s_set_slot_ctrl(i2s->regmap, channels,
+ i2s->tx_mask, i2s->rx_mask);
+ break;
+ default:
+ dev_err(dev, "invalid I2S frame format\n");
+ return -EINVAL;
+ }
+
+ if (bit_count > I2S_TIMING_CH_BIT_CNT_MASK) {
+ dev_err(dev, "invalid I2S channel bit count %u\n", bit_count);
+ return -EINVAL;
+ }
+
+ regmap_write(i2s->regmap, TEGRA210_I2S_TIMING,
+ bit_count << I2S_TIMING_CH_BIT_CNT_SHIFT);
+
+ return 0;
+}
+
+static int tegra210_i2s_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_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ unsigned int sample_size, channels, srate, val, reg, path;
+ struct tegra_cif_conf cif_conf;
+
+ memset(&cif_conf, 0, sizeof(struct tegra_cif_conf));
+
+ channels = params_channels(params);
+ if (channels < 1) {
+ dev_err(dev, "invalid I2S %d channel configuration\n",
+ channels);
+ return -EINVAL;
+ }
+
+ cif_conf.audio_ch = channels;
+ cif_conf.client_ch = channels;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ val = I2S_BITS_8;
+ sample_size = 8;
+ cif_conf.audio_bits = TEGRA_ACIF_BITS_8;
+ cif_conf.client_bits = TEGRA_ACIF_BITS_8;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ val = I2S_BITS_16;
+ sample_size = 16;
+ cif_conf.audio_bits = TEGRA_ACIF_BITS_16;
+ cif_conf.client_bits = TEGRA_ACIF_BITS_16;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ val = I2S_BITS_32;
+ sample_size = 32;
+ cif_conf.audio_bits = TEGRA_ACIF_BITS_32;
+ cif_conf.client_bits = TEGRA_ACIF_BITS_32;
+ break;
+ default:
+ dev_err(dev, "unsupported format!\n");
+ return -EOPNOTSUPP;
+ }
+
+ /* Program sample size */
+ regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL,
+ I2S_CTRL_BIT_SIZE_MASK, val);
+
+ srate = params_rate(params);
+
+ /* For playback I2S RX-CIF and for capture TX-CIF is used */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ path = I2S_RX_PATH;
+ else
+ path = I2S_TX_PATH;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ unsigned int max_th;
+
+ /* FIFO threshold in terms of frames */
+ max_th = (I2S_RX_FIFO_DEPTH / cif_conf.audio_ch) - 1;
+
+ if (i2s->rx_fifo_th > max_th)
+ i2s->rx_fifo_th = max_th;
+
+ cif_conf.threshold = i2s->rx_fifo_th;
+
+ reg = TEGRA210_I2S_RX_CIF_CTRL;
+ } else {
+ reg = TEGRA210_I2S_TX_CIF_CTRL;
+ }
+
+ cif_conf.mono_conv = i2s->mono_to_stereo[path];
+ cif_conf.stereo_conv = i2s->stereo_to_mono[path];
+
+ tegra_set_cif(i2s->regmap, reg, &cif_conf);
+
+ return tegra210_i2s_set_timing_params(dev, sample_size, srate,
+ cif_conf.client_ch);
+}
+
+static const struct snd_soc_dai_ops tegra210_i2s_dai_ops = {
+ .set_fmt = tegra210_i2s_set_fmt,
+ .hw_params = tegra210_i2s_hw_params,
+ .set_bclk_ratio = tegra210_i2s_set_dai_bclk_ratio,
+ .set_tdm_slot = tegra210_i2s_set_tdm_slot,
+};
+
+static struct snd_soc_dai_driver tegra210_i2s_dais[] = {
+ {
+ .name = "I2S-CIF",
+ .playback = {
+ .stream_name = "CIF-Playback",
+ .channels_min = 1,
+ .channels_max = 16,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .capture = {
+ .stream_name = "CIF-Capture",
+ .channels_min = 1,
+ .channels_max = 16,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ },
+ {
+ .name = "I2S-DAP",
+ .playback = {
+ .stream_name = "DAP-Playback",
+ .channels_min = 1,
+ .channels_max = 16,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .capture = {
+ .stream_name = "DAP-Capture",
+ .channels_min = 1,
+ .channels_max = 16,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &tegra210_i2s_dai_ops,
+ .symmetric_rate = 1,
+ },
+};
+
+static const char * const tegra210_i2s_stereo_conv_text[] = {
+ "CH0", "CH1", "AVG",
+};
+
+static const char * const tegra210_i2s_mono_conv_text[] = {
+ "Zero", "Copy",
+};
+
+static const struct soc_enum tegra210_i2s_mono_conv_enum =
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_i2s_mono_conv_text),
+ tegra210_i2s_mono_conv_text);
+
+static const struct soc_enum tegra210_i2s_stereo_conv_enum =
+ SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(tegra210_i2s_stereo_conv_text),
+ tegra210_i2s_stereo_conv_text);
+
+static const struct snd_kcontrol_new tegra210_i2s_controls[] = {
+ SOC_SINGLE_EXT("Loopback", 0, 0, 1, 0, tegra210_i2s_get_loopback,
+ tegra210_i2s_put_loopback),
+ SOC_SINGLE_EXT("FSYNC Width", 0, 0, 255, 0,
+ tegra210_i2s_get_fsync_width,
+ tegra210_i2s_put_fsync_width),
+ SOC_ENUM_EXT("Capture Stereo To Mono", tegra210_i2s_stereo_conv_enum,
+ tegra210_i2s_cget_stereo_to_mono,
+ tegra210_i2s_cput_stereo_to_mono),
+ SOC_ENUM_EXT("Capture Mono To Stereo", tegra210_i2s_mono_conv_enum,
+ tegra210_i2s_cget_mono_to_stereo,
+ tegra210_i2s_cput_mono_to_stereo),
+ SOC_ENUM_EXT("Playback Stereo To Mono", tegra210_i2s_stereo_conv_enum,
+ tegra210_i2s_pget_mono_to_stereo,
+ tegra210_i2s_pput_mono_to_stereo),
+ SOC_ENUM_EXT("Playback Mono To Stereo", tegra210_i2s_mono_conv_enum,
+ tegra210_i2s_pget_stereo_to_mono,
+ tegra210_i2s_pput_stereo_to_mono),
+ SOC_SINGLE_EXT("Playback FIFO Threshold", 0, 0, I2S_RX_FIFO_DEPTH - 1,
+ 0, tegra210_i2s_pget_fifo_th, tegra210_i2s_pput_fifo_th),
+ SOC_SINGLE_EXT("BCLK Ratio", 0, 0, INT_MAX, 0,
+ tegra210_i2s_get_bclk_ratio,
+ tegra210_i2s_put_bclk_ratio),
+};
+
+static const struct snd_soc_dapm_widget tegra210_i2s_widgets[] = {
+ SND_SOC_DAPM_AIF_IN_E("RX", NULL, 0, TEGRA210_I2S_RX_ENABLE,
+ 0, 0, tegra210_i2s_init, SND_SOC_DAPM_PRE_PMU),
+ SND_SOC_DAPM_AIF_OUT_E("TX", NULL, 0, TEGRA210_I2S_TX_ENABLE,
+ 0, 0, tegra210_i2s_init, SND_SOC_DAPM_PRE_PMU),
+ SND_SOC_DAPM_MIC("MIC", NULL),
+ SND_SOC_DAPM_SPK("SPK", NULL),
+};
+
+static const struct snd_soc_dapm_route tegra210_i2s_routes[] = {
+ /* Playback route from XBAR */
+ { "XBAR-Playback", NULL, "XBAR-TX" },
+ { "CIF-Playback", NULL, "XBAR-Playback" },
+ { "RX", NULL, "CIF-Playback" },
+ { "DAP-Playback", NULL, "RX" },
+ { "SPK", NULL, "DAP-Playback" },
+ /* Capture route to XBAR */
+ { "XBAR-RX", NULL, "XBAR-Capture" },
+ { "XBAR-Capture", NULL, "CIF-Capture" },
+ { "CIF-Capture", NULL, "TX" },
+ { "TX", NULL, "DAP-Capture" },
+ { "DAP-Capture", NULL, "MIC" },
+};
+
+static const struct snd_soc_component_driver tegra210_i2s_cmpnt = {
+ .dapm_widgets = tegra210_i2s_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra210_i2s_widgets),
+ .dapm_routes = tegra210_i2s_routes,
+ .num_dapm_routes = ARRAY_SIZE(tegra210_i2s_routes),
+ .controls = tegra210_i2s_controls,
+ .num_controls = ARRAY_SIZE(tegra210_i2s_controls),
+};
+
+static bool tegra210_i2s_wr_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_I2S_RX_ENABLE ... TEGRA210_I2S_RX_SOFT_RESET:
+ case TEGRA210_I2S_RX_INT_MASK ... TEGRA210_I2S_RX_CLK_TRIM:
+ case TEGRA210_I2S_TX_ENABLE ... TEGRA210_I2S_TX_SOFT_RESET:
+ case TEGRA210_I2S_TX_INT_MASK ... TEGRA210_I2S_TX_CLK_TRIM:
+ case TEGRA210_I2S_ENABLE ... TEGRA210_I2S_CG:
+ case TEGRA210_I2S_CTRL ... TEGRA210_I2S_CYA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_i2s_rd_reg(struct device *dev, unsigned int reg)
+{
+ if (tegra210_i2s_wr_reg(dev, reg))
+ return true;
+
+ switch (reg) {
+ case TEGRA210_I2S_RX_STATUS:
+ case TEGRA210_I2S_RX_INT_STATUS:
+ case TEGRA210_I2S_RX_CIF_FIFO_STATUS:
+ case TEGRA210_I2S_TX_STATUS:
+ case TEGRA210_I2S_TX_INT_STATUS:
+ case TEGRA210_I2S_TX_CIF_FIFO_STATUS:
+ case TEGRA210_I2S_STATUS:
+ case TEGRA210_I2S_INT_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_i2s_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_I2S_RX_STATUS:
+ case TEGRA210_I2S_RX_INT_STATUS:
+ case TEGRA210_I2S_RX_CIF_FIFO_STATUS:
+ case TEGRA210_I2S_TX_STATUS:
+ case TEGRA210_I2S_TX_INT_STATUS:
+ case TEGRA210_I2S_TX_CIF_FIFO_STATUS:
+ case TEGRA210_I2S_STATUS:
+ case TEGRA210_I2S_INT_STATUS:
+ case TEGRA210_I2S_RX_SOFT_RESET:
+ case TEGRA210_I2S_TX_SOFT_RESET:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tegra210_i2s_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA210_I2S_CYA,
+ .writeable_reg = tegra210_i2s_wr_reg,
+ .readable_reg = tegra210_i2s_rd_reg,
+ .volatile_reg = tegra210_i2s_volatile_reg,
+ .reg_defaults = tegra210_i2s_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tegra210_i2s_reg_defaults),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int tegra210_i2s_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tegra210_i2s *i2s;
+ void __iomem *regs;
+ int err;
+
+ i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL);
+ if (!i2s)
+ return -ENOMEM;
+
+ i2s->rx_fifo_th = DEFAULT_I2S_RX_FIFO_THRESHOLD;
+ i2s->tx_mask = DEFAULT_I2S_SLOT_MASK;
+ i2s->rx_mask = DEFAULT_I2S_SLOT_MASK;
+ i2s->loopback = false;
+
+ dev_set_drvdata(dev, i2s);
+
+ i2s->clk_i2s = devm_clk_get(dev, "i2s");
+ if (IS_ERR(i2s->clk_i2s)) {
+ dev_err(dev, "can't retrieve I2S bit clock\n");
+ return PTR_ERR(i2s->clk_i2s);
+ }
+
+ /*
+ * Not an error, as this clock is needed only when some other I/O
+ * requires input clock from current I2S instance, which is
+ * configurable from DT.
+ */
+ i2s->clk_sync_input = devm_clk_get(dev, "sync_input");
+ if (IS_ERR(i2s->clk_sync_input))
+ dev_dbg(dev, "can't retrieve I2S sync input clock\n");
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ i2s->regmap = devm_regmap_init_mmio(dev, regs,
+ &tegra210_i2s_regmap_config);
+ if (IS_ERR(i2s->regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ return PTR_ERR(i2s->regmap);
+ }
+
+ regcache_cache_only(i2s->regmap, true);
+
+ err = devm_snd_soc_register_component(dev, &tegra210_i2s_cmpnt,
+ tegra210_i2s_dais,
+ ARRAY_SIZE(tegra210_i2s_dais));
+ if (err) {
+ dev_err(dev, "can't register I2S component, err: %d\n", err);
+ return err;
+ }
+
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static int tegra210_i2s_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra210_i2s_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra210_i2s_runtime_suspend,
+ tegra210_i2s_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static const struct of_device_id tegra210_i2s_of_match[] = {
+ { .compatible = "nvidia,tegra210-i2s" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra210_i2s_of_match);
+
+static struct platform_driver tegra210_i2s_driver = {
+ .driver = {
+ .name = "tegra210-i2s",
+ .of_match_table = tegra210_i2s_of_match,
+ .pm = &tegra210_i2s_pm_ops,
+ },
+ .probe = tegra210_i2s_probe,
+ .remove = tegra210_i2s_remove,
+};
+module_platform_driver(tegra210_i2s_driver)
+
+MODULE_AUTHOR("Songhee Baek <sbaek@nvidia.com>");
+MODULE_DESCRIPTION("Tegra210 ASoC I2S driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/tegra/tegra210_i2s.h b/sound/soc/tegra/tegra210_i2s.h
new file mode 100644
index 000000000..030d70c45
--- /dev/null
+++ b/sound/soc/tegra/tegra210_i2s.h
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra210_i2s.h - Definitions for Tegra210 I2S driver
+ *
+ * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved.
+ *
+ */
+
+#ifndef __TEGRA210_I2S_H__
+#define __TEGRA210_I2S_H__
+
+/* Register offsets from I2S*_BASE */
+#define TEGRA210_I2S_RX_ENABLE 0x0
+#define TEGRA210_I2S_RX_SOFT_RESET 0x4
+#define TEGRA210_I2S_RX_STATUS 0x0c
+#define TEGRA210_I2S_RX_INT_STATUS 0x10
+#define TEGRA210_I2S_RX_INT_MASK 0x14
+#define TEGRA210_I2S_RX_INT_SET 0x18
+#define TEGRA210_I2S_RX_INT_CLEAR 0x1c
+#define TEGRA210_I2S_RX_CIF_CTRL 0x20
+#define TEGRA210_I2S_RX_CTRL 0x24
+#define TEGRA210_I2S_RX_SLOT_CTRL 0x28
+#define TEGRA210_I2S_RX_CLK_TRIM 0x2c
+#define TEGRA210_I2S_RX_CYA 0x30
+#define TEGRA210_I2S_RX_CIF_FIFO_STATUS 0x34
+#define TEGRA210_I2S_TX_ENABLE 0x40
+#define TEGRA210_I2S_TX_SOFT_RESET 0x44
+#define TEGRA210_I2S_TX_STATUS 0x4c
+#define TEGRA210_I2S_TX_INT_STATUS 0x50
+#define TEGRA210_I2S_TX_INT_MASK 0x54
+#define TEGRA210_I2S_TX_INT_SET 0x58
+#define TEGRA210_I2S_TX_INT_CLEAR 0x5c
+#define TEGRA210_I2S_TX_CIF_CTRL 0x60
+#define TEGRA210_I2S_TX_CTRL 0x64
+#define TEGRA210_I2S_TX_SLOT_CTRL 0x68
+#define TEGRA210_I2S_TX_CLK_TRIM 0x6c
+#define TEGRA210_I2S_TX_CYA 0x70
+#define TEGRA210_I2S_TX_CIF_FIFO_STATUS 0x74
+#define TEGRA210_I2S_ENABLE 0x80
+#define TEGRA210_I2S_SOFT_RESET 0x84
+#define TEGRA210_I2S_CG 0x88
+#define TEGRA210_I2S_STATUS 0x8c
+#define TEGRA210_I2S_INT_STATUS 0x90
+#define TEGRA210_I2S_CTRL 0xa0
+#define TEGRA210_I2S_TIMING 0xa4
+#define TEGRA210_I2S_SLOT_CTRL 0xa8
+#define TEGRA210_I2S_CLK_TRIM 0xac
+#define TEGRA210_I2S_CYA 0xb0
+
+/* Bit fields, shifts and masks */
+#define I2S_DATA_SHIFT 8
+#define I2S_CTRL_DATA_OFFSET_MASK (0x7ff << I2S_DATA_SHIFT)
+
+#define I2S_EN_SHIFT 0
+#define I2S_EN_MASK BIT(I2S_EN_SHIFT)
+#define I2S_EN BIT(I2S_EN_SHIFT)
+
+#define I2S_FSYNC_WIDTH_SHIFT 24
+#define I2S_CTRL_FSYNC_WIDTH_MASK (0xff << I2S_FSYNC_WIDTH_SHIFT)
+
+#define I2S_POS_EDGE 0
+#define I2S_NEG_EDGE 1
+#define I2S_EDGE_SHIFT 20
+#define I2S_CTRL_EDGE_CTRL_MASK BIT(I2S_EDGE_SHIFT)
+#define I2S_CTRL_EDGE_CTRL_POS_EDGE (I2S_POS_EDGE << I2S_EDGE_SHIFT)
+#define I2S_CTRL_EDGE_CTRL_NEG_EDGE (I2S_NEG_EDGE << I2S_EDGE_SHIFT)
+
+#define I2S_FMT_LRCK 0
+#define I2S_FMT_FSYNC 1
+#define I2S_FMT_SHIFT 12
+#define I2S_CTRL_FRAME_FMT_MASK (7 << I2S_FMT_SHIFT)
+#define I2S_CTRL_FRAME_FMT_LRCK_MODE (I2S_FMT_LRCK << I2S_FMT_SHIFT)
+#define I2S_CTRL_FRAME_FMT_FSYNC_MODE (I2S_FMT_FSYNC << I2S_FMT_SHIFT)
+
+#define I2S_CTRL_MASTER_EN_SHIFT 10
+#define I2S_CTRL_MASTER_EN_MASK BIT(I2S_CTRL_MASTER_EN_SHIFT)
+#define I2S_CTRL_MASTER_EN BIT(I2S_CTRL_MASTER_EN_SHIFT)
+
+#define I2S_CTRL_LRCK_POL_SHIFT 9
+#define I2S_CTRL_LRCK_POL_MASK BIT(I2S_CTRL_LRCK_POL_SHIFT)
+#define I2S_CTRL_LRCK_POL_LOW (0 << I2S_CTRL_LRCK_POL_SHIFT)
+#define I2S_CTRL_LRCK_POL_HIGH BIT(I2S_CTRL_LRCK_POL_SHIFT)
+
+#define I2S_CTRL_LPBK_SHIFT 8
+#define I2S_CTRL_LPBK_MASK BIT(I2S_CTRL_LPBK_SHIFT)
+#define I2S_CTRL_LPBK_EN BIT(I2S_CTRL_LPBK_SHIFT)
+
+#define I2S_BITS_8 1
+#define I2S_BITS_16 3
+#define I2S_BITS_32 7
+#define I2S_CTRL_BIT_SIZE_MASK 0x7
+
+#define I2S_TIMING_CH_BIT_CNT_MASK 0x7ff
+#define I2S_TIMING_CH_BIT_CNT_SHIFT 0
+
+#define I2S_SOFT_RESET_SHIFT 0
+#define I2S_SOFT_RESET_MASK BIT(I2S_SOFT_RESET_SHIFT)
+#define I2S_SOFT_RESET_EN BIT(I2S_SOFT_RESET_SHIFT)
+
+#define I2S_RX_FIFO_DEPTH 64
+#define DEFAULT_I2S_RX_FIFO_THRESHOLD 3
+
+#define DEFAULT_I2S_SLOT_MASK 0xffff
+
+enum tegra210_i2s_path {
+ I2S_RX_PATH,
+ I2S_TX_PATH,
+ I2S_PATHS,
+};
+
+struct tegra210_i2s {
+ struct clk *clk_i2s;
+ struct clk *clk_sync_input;
+ struct regmap *regmap;
+ unsigned int stereo_to_mono[I2S_PATHS];
+ unsigned int mono_to_stereo[I2S_PATHS];
+ unsigned int dai_fmt;
+ unsigned int fsync_width;
+ unsigned int bclk_ratio;
+ unsigned int tx_mask;
+ unsigned int rx_mask;
+ unsigned int rx_fifo_th;
+ bool loopback;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra210_mbdrc.c b/sound/soc/tegra/tegra210_mbdrc.c
new file mode 100644
index 000000000..d786daa6a
--- /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 *)&params->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 *)&params->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 000000000..4c48da0e1
--- /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_mixer.c b/sound/soc/tegra/tegra210_mixer.c
new file mode 100644
index 000000000..16e679a95
--- /dev/null
+++ b/sound/soc/tegra/tegra210_mixer.c
@@ -0,0 +1,686 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// tegra210_mixer.c - Tegra210 MIXER driver
+//
+// Copyright (c) 2021 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_mixer.h"
+#include "tegra_cif.h"
+
+#define MIXER_REG(reg, id) ((reg) + ((id) * TEGRA210_MIXER_REG_STRIDE))
+#define MIXER_REG_BASE(reg) ((reg) % TEGRA210_MIXER_REG_STRIDE)
+
+#define MIXER_GAIN_CFG_RAM_ADDR(id) \
+ (TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0 + \
+ ((id) * TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE))
+
+#define MIXER_RX_REG_DEFAULTS(id) \
+ { MIXER_REG(TEGRA210_MIXER_RX1_CIF_CTRL, id), 0x00007700}, \
+ { MIXER_REG(TEGRA210_MIXER_RX1_CTRL, id), 0x00010823}, \
+ { MIXER_REG(TEGRA210_MIXER_RX1_PEAK_CTRL, id), 0x000012c0}
+
+#define MIXER_TX_REG_DEFAULTS(id) \
+ { MIXER_REG(TEGRA210_MIXER_TX1_INT_MASK, (id)), 0x00000001}, \
+ { MIXER_REG(TEGRA210_MIXER_TX1_CIF_CTRL, (id)), 0x00007700}
+
+#define REG_DURATION_PARAM(reg, i) ((reg) + NUM_GAIN_POLY_COEFFS + 1 + (i))
+
+static const struct reg_default tegra210_mixer_reg_defaults[] = {
+ /* Inputs */
+ MIXER_RX_REG_DEFAULTS(0),
+ MIXER_RX_REG_DEFAULTS(1),
+ MIXER_RX_REG_DEFAULTS(2),
+ MIXER_RX_REG_DEFAULTS(3),
+ MIXER_RX_REG_DEFAULTS(4),
+ MIXER_RX_REG_DEFAULTS(5),
+ MIXER_RX_REG_DEFAULTS(6),
+ MIXER_RX_REG_DEFAULTS(7),
+ MIXER_RX_REG_DEFAULTS(8),
+ MIXER_RX_REG_DEFAULTS(9),
+ /* Outputs */
+ MIXER_TX_REG_DEFAULTS(0),
+ MIXER_TX_REG_DEFAULTS(1),
+ MIXER_TX_REG_DEFAULTS(2),
+ MIXER_TX_REG_DEFAULTS(3),
+ MIXER_TX_REG_DEFAULTS(4),
+
+ { TEGRA210_MIXER_CG, 0x00000001},
+ { TEGRA210_MIXER_GAIN_CFG_RAM_CTRL, 0x00004000},
+ { TEGRA210_MIXER_PEAKM_RAM_CTRL, 0x00004000},
+ { TEGRA210_MIXER_ENABLE, 0x1 },
+};
+
+/* Default gain parameters */
+static const struct tegra210_mixer_gain_params gain_params = {
+ /* Polynomial coefficients */
+ { 0, 0, 0, 0, 0, 0, 0, 0x1000000, 0 },
+ /* Gain value */
+ 0x10000,
+ /* Duration Parameters */
+ { 0, 0, 0x400, 0x8000000 },
+};
+
+static int __maybe_unused tegra210_mixer_runtime_suspend(struct device *dev)
+{
+ struct tegra210_mixer *mixer = dev_get_drvdata(dev);
+
+ regcache_cache_only(mixer->regmap, true);
+ regcache_mark_dirty(mixer->regmap);
+
+ return 0;
+}
+
+static int __maybe_unused tegra210_mixer_runtime_resume(struct device *dev)
+{
+ struct tegra210_mixer *mixer = dev_get_drvdata(dev);
+
+ regcache_cache_only(mixer->regmap, false);
+ regcache_sync(mixer->regmap);
+
+ return 0;
+}
+
+static int tegra210_mixer_write_ram(struct tegra210_mixer *mixer,
+ unsigned int addr,
+ unsigned int coef)
+{
+ unsigned int reg, val;
+ int err;
+
+ /* Check if busy */
+ err = regmap_read_poll_timeout(mixer->regmap,
+ TEGRA210_MIXER_GAIN_CFG_RAM_CTRL,
+ val, !(val & 0x80000000), 10, 10000);
+ if (err < 0)
+ return err;
+
+ reg = (addr << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_SHIFT) &
+ TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_MASK;
+ reg |= TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN;
+ reg |= TEGRA210_MIXER_GAIN_CFG_RAM_RW_WRITE;
+ reg |= TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN;
+
+ regmap_write(mixer->regmap,
+ TEGRA210_MIXER_GAIN_CFG_RAM_CTRL,
+ reg);
+ regmap_write(mixer->regmap,
+ TEGRA210_MIXER_GAIN_CFG_RAM_DATA,
+ coef);
+
+ return 0;
+}
+
+static int tegra210_mixer_configure_gain(struct snd_soc_component *cmpnt,
+ unsigned int id, bool instant_gain)
+{
+ struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
+ unsigned int reg = MIXER_GAIN_CFG_RAM_ADDR(id);
+ int err, i;
+
+ pm_runtime_get_sync(cmpnt->dev);
+
+ /* Write default gain poly coefficients */
+ for (i = 0; i < NUM_GAIN_POLY_COEFFS; i++) {
+ err = tegra210_mixer_write_ram(mixer, reg + i,
+ gain_params.poly_coeff[i]);
+
+ if (err < 0)
+ goto rpm_put;
+ }
+
+ /* Write stored gain value */
+ err = tegra210_mixer_write_ram(mixer, reg + NUM_GAIN_POLY_COEFFS,
+ mixer->gain_value[id]);
+ if (err < 0)
+ goto rpm_put;
+
+ /* Write duration parameters */
+ for (i = 0; i < NUM_DURATION_PARMS; i++) {
+ int val;
+
+ if (instant_gain)
+ val = 1;
+ else
+ val = gain_params.duration[i];
+
+ err = tegra210_mixer_write_ram(mixer,
+ REG_DURATION_PARAM(reg, i),
+ val);
+ if (err < 0)
+ goto rpm_put;
+ }
+
+ /* Trigger to apply gain configurations */
+ err = tegra210_mixer_write_ram(mixer, reg + REG_CFG_DONE_TRIGGER,
+ VAL_CFG_DONE_TRIGGER);
+
+rpm_put:
+ pm_runtime_put(cmpnt->dev);
+
+ return err;
+}
+
+static int tegra210_mixer_get_gain(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_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
+ unsigned int reg = mc->reg;
+ unsigned int i;
+
+ i = (reg - TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0) /
+ TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE;
+
+ ucontrol->value.integer.value[0] = mixer->gain_value[i];
+
+ return 0;
+}
+
+static int tegra210_mixer_apply_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol,
+ bool instant_gain)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_mixer *mixer = snd_soc_component_get_drvdata(cmpnt);
+ unsigned int reg = mc->reg, id;
+ int err;
+
+ /* Save gain value for specific MIXER input */
+ id = (reg - TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0) /
+ TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE;
+
+ if (mixer->gain_value[id] == ucontrol->value.integer.value[0])
+ return 0;
+
+ mixer->gain_value[id] = ucontrol->value.integer.value[0];
+
+ err = tegra210_mixer_configure_gain(cmpnt, id, instant_gain);
+ if (err) {
+ dev_err(cmpnt->dev, "Failed to apply gain\n");
+ return err;
+ }
+
+ return 1;
+}
+
+static int tegra210_mixer_put_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return tegra210_mixer_apply_gain(kcontrol, ucontrol, false);
+}
+
+static int tegra210_mixer_put_instant_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return tegra210_mixer_apply_gain(kcontrol, ucontrol, true);
+}
+
+static int tegra210_mixer_set_audio_cif(struct tegra210_mixer *mixer,
+ struct snd_pcm_hw_params *params,
+ unsigned int reg,
+ unsigned int id)
+{
+ unsigned 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_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(mixer->regmap,
+ reg + (id * TEGRA210_MIXER_REG_STRIDE),
+ &cif_conf);
+
+ return 0;
+}
+
+static int tegra210_mixer_in_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct tegra210_mixer *mixer = snd_soc_dai_get_drvdata(dai);
+ int err;
+
+ err = tegra210_mixer_set_audio_cif(mixer, params,
+ TEGRA210_MIXER_RX1_CIF_CTRL,
+ dai->id);
+ if (err < 0)
+ return err;
+
+ return tegra210_mixer_configure_gain(dai->component, dai->id, false);
+}
+
+static int tegra210_mixer_out_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct tegra210_mixer *mixer = snd_soc_dai_get_drvdata(dai);
+
+ return tegra210_mixer_set_audio_cif(mixer, params,
+ TEGRA210_MIXER_TX1_CIF_CTRL,
+ dai->id - TEGRA210_MIXER_RX_MAX);
+}
+
+static const struct snd_soc_dai_ops tegra210_mixer_out_dai_ops = {
+ .hw_params = tegra210_mixer_out_hw_params,
+};
+
+static const struct snd_soc_dai_ops tegra210_mixer_in_dai_ops = {
+ .hw_params = tegra210_mixer_in_hw_params,
+};
+
+#define IN_DAI(id) \
+ { \
+ .name = "MIXER-RX-CIF"#id, \
+ .playback = { \
+ .stream_name = "RX" #id "-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" #id "-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_mixer_in_dai_ops, \
+ }
+
+#define OUT_DAI(id) \
+ { \
+ .name = "MIXER-TX-CIF" #id, \
+ .playback = { \
+ .stream_name = "TX" #id "-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" #id "-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_mixer_out_dai_ops, \
+ }
+
+static struct snd_soc_dai_driver tegra210_mixer_dais[] = {
+ /* Mixer Input */
+ IN_DAI(1),
+ IN_DAI(2),
+ IN_DAI(3),
+ IN_DAI(4),
+ IN_DAI(5),
+ IN_DAI(6),
+ IN_DAI(7),
+ IN_DAI(8),
+ IN_DAI(9),
+ IN_DAI(10),
+
+ /* Mixer Output */
+ OUT_DAI(1),
+ OUT_DAI(2),
+ OUT_DAI(3),
+ OUT_DAI(4),
+ OUT_DAI(5),
+};
+
+#define ADDER_CTRL_DECL(name, reg) \
+ static const struct snd_kcontrol_new name[] = { \
+ SOC_DAPM_SINGLE("RX1", reg, 0, 1, 0), \
+ SOC_DAPM_SINGLE("RX2", reg, 1, 1, 0), \
+ SOC_DAPM_SINGLE("RX3", reg, 2, 1, 0), \
+ SOC_DAPM_SINGLE("RX4", reg, 3, 1, 0), \
+ SOC_DAPM_SINGLE("RX5", reg, 4, 1, 0), \
+ SOC_DAPM_SINGLE("RX6", reg, 5, 1, 0), \
+ SOC_DAPM_SINGLE("RX7", reg, 6, 1, 0), \
+ SOC_DAPM_SINGLE("RX8", reg, 7, 1, 0), \
+ SOC_DAPM_SINGLE("RX9", reg, 8, 1, 0), \
+ SOC_DAPM_SINGLE("RX10", reg, 9, 1, 0), \
+ }
+
+ADDER_CTRL_DECL(adder1, TEGRA210_MIXER_TX1_ADDER_CONFIG);
+ADDER_CTRL_DECL(adder2, TEGRA210_MIXER_TX2_ADDER_CONFIG);
+ADDER_CTRL_DECL(adder3, TEGRA210_MIXER_TX3_ADDER_CONFIG);
+ADDER_CTRL_DECL(adder4, TEGRA210_MIXER_TX4_ADDER_CONFIG);
+ADDER_CTRL_DECL(adder5, TEGRA210_MIXER_TX5_ADDER_CONFIG);
+
+#define GAIN_CTRL(id) \
+ SOC_SINGLE_EXT("RX" #id " Gain Volume", \
+ MIXER_GAIN_CFG_RAM_ADDR((id) - 1), 0, \
+ 0x20000, 0, tegra210_mixer_get_gain, \
+ tegra210_mixer_put_gain), \
+ SOC_SINGLE_EXT("RX" #id " Instant Gain Volume", \
+ MIXER_GAIN_CFG_RAM_ADDR((id) - 1), 0, \
+ 0x20000, 0, tegra210_mixer_get_gain, \
+ tegra210_mixer_put_instant_gain),
+
+/* Volume controls for all MIXER inputs */
+static const struct snd_kcontrol_new tegra210_mixer_gain_ctls[] = {
+ GAIN_CTRL(1)
+ GAIN_CTRL(2)
+ GAIN_CTRL(3)
+ GAIN_CTRL(4)
+ GAIN_CTRL(5)
+ GAIN_CTRL(6)
+ GAIN_CTRL(7)
+ GAIN_CTRL(8)
+ GAIN_CTRL(9)
+ GAIN_CTRL(10)
+};
+
+static const struct snd_soc_dapm_widget tegra210_mixer_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_IN("RX8", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("RX9", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("RX10", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX1", NULL, 0, TEGRA210_MIXER_TX1_ENABLE, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX2", NULL, 0, TEGRA210_MIXER_TX2_ENABLE, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX3", NULL, 0, TEGRA210_MIXER_TX3_ENABLE, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX4", NULL, 0, TEGRA210_MIXER_TX4_ENABLE, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX5", NULL, 0, TEGRA210_MIXER_TX5_ENABLE, 0, 0),
+ SND_SOC_DAPM_MIXER("Adder1", SND_SOC_NOPM, 1, 0, adder1,
+ ARRAY_SIZE(adder1)),
+ SND_SOC_DAPM_MIXER("Adder2", SND_SOC_NOPM, 1, 0, adder2,
+ ARRAY_SIZE(adder2)),
+ SND_SOC_DAPM_MIXER("Adder3", SND_SOC_NOPM, 1, 0, adder3,
+ ARRAY_SIZE(adder3)),
+ SND_SOC_DAPM_MIXER("Adder4", SND_SOC_NOPM, 1, 0, adder4,
+ ARRAY_SIZE(adder4)),
+ SND_SOC_DAPM_MIXER("Adder5", SND_SOC_NOPM, 1, 0, adder5,
+ ARRAY_SIZE(adder5)),
+};
+
+#define RX_ROUTES(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 }
+
+#define MIXER_RX_ROUTES(id) \
+ RX_ROUTES(id, "Playback"), \
+ RX_ROUTES(id, "Capture")
+
+#define ADDER_ROUTES(id, sname) \
+ { "Adder" #id, "RX1", "RX1" }, \
+ { "Adder" #id, "RX2", "RX2" }, \
+ { "Adder" #id, "RX3", "RX3" }, \
+ { "Adder" #id, "RX4", "RX4" }, \
+ { "Adder" #id, "RX5", "RX5" }, \
+ { "Adder" #id, "RX6", "RX6" }, \
+ { "Adder" #id, "RX7", "RX7" }, \
+ { "Adder" #id, "RX8", "RX8" }, \
+ { "Adder" #id, "RX9", "RX9" }, \
+ { "Adder" #id, "RX10", "RX10" }, \
+ { "TX" #id, NULL, "Adder" #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 TX_ROUTES(id, sname) \
+ ADDER_ROUTES(1, sname), \
+ ADDER_ROUTES(2, sname), \
+ ADDER_ROUTES(3, sname), \
+ ADDER_ROUTES(4, sname), \
+ ADDER_ROUTES(5, sname)
+
+#define MIXER_TX_ROUTES(id) \
+ TX_ROUTES(id, "Playback"), \
+ TX_ROUTES(id, "Capture")
+
+static const struct snd_soc_dapm_route tegra210_mixer_routes[] = {
+ /* Input */
+ MIXER_RX_ROUTES(1),
+ MIXER_RX_ROUTES(2),
+ MIXER_RX_ROUTES(3),
+ MIXER_RX_ROUTES(4),
+ MIXER_RX_ROUTES(5),
+ MIXER_RX_ROUTES(6),
+ MIXER_RX_ROUTES(7),
+ MIXER_RX_ROUTES(8),
+ MIXER_RX_ROUTES(9),
+ MIXER_RX_ROUTES(10),
+ /* Output */
+ MIXER_TX_ROUTES(1),
+ MIXER_TX_ROUTES(2),
+ MIXER_TX_ROUTES(3),
+ MIXER_TX_ROUTES(4),
+ MIXER_TX_ROUTES(5),
+};
+
+static const struct snd_soc_component_driver tegra210_mixer_cmpnt = {
+ .dapm_widgets = tegra210_mixer_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra210_mixer_widgets),
+ .dapm_routes = tegra210_mixer_routes,
+ .num_dapm_routes = ARRAY_SIZE(tegra210_mixer_routes),
+ .controls = tegra210_mixer_gain_ctls,
+ .num_controls = ARRAY_SIZE(tegra210_mixer_gain_ctls),
+};
+
+static bool tegra210_mixer_wr_reg(struct device *dev,
+ unsigned int reg)
+{
+ if (reg < TEGRA210_MIXER_RX_LIMIT)
+ reg = MIXER_REG_BASE(reg);
+ else if (reg < TEGRA210_MIXER_TX_LIMIT)
+ reg = MIXER_REG_BASE(reg) + TEGRA210_MIXER_TX1_ENABLE;
+
+ switch (reg) {
+ case TEGRA210_MIXER_RX1_SOFT_RESET:
+ case TEGRA210_MIXER_RX1_CIF_CTRL ... TEGRA210_MIXER_RX1_PEAK_CTRL:
+
+ case TEGRA210_MIXER_TX1_ENABLE:
+ case TEGRA210_MIXER_TX1_SOFT_RESET:
+ case TEGRA210_MIXER_TX1_INT_MASK ... TEGRA210_MIXER_TX1_ADDER_CONFIG:
+
+ case TEGRA210_MIXER_ENABLE ... TEGRA210_MIXER_CG:
+ case TEGRA210_MIXER_GAIN_CFG_RAM_CTRL ... TEGRA210_MIXER_CTRL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_mixer_rd_reg(struct device *dev,
+ unsigned int reg)
+{
+ if (reg < TEGRA210_MIXER_RX_LIMIT)
+ reg = MIXER_REG_BASE(reg);
+ else if (reg < TEGRA210_MIXER_TX_LIMIT)
+ reg = MIXER_REG_BASE(reg) + TEGRA210_MIXER_TX1_ENABLE;
+
+ switch (reg) {
+ case TEGRA210_MIXER_RX1_SOFT_RESET ... TEGRA210_MIXER_RX1_SAMPLE_COUNT:
+ case TEGRA210_MIXER_TX1_ENABLE ... TEGRA210_MIXER_TX1_ADDER_CONFIG:
+ case TEGRA210_MIXER_ENABLE ... TEGRA210_MIXER_CTRL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_mixer_volatile_reg(struct device *dev,
+ unsigned int reg)
+{
+ if (reg < TEGRA210_MIXER_RX_LIMIT)
+ reg = MIXER_REG_BASE(reg);
+ else if (reg < TEGRA210_MIXER_TX_LIMIT)
+ reg = MIXER_REG_BASE(reg) + TEGRA210_MIXER_TX1_ENABLE;
+
+ switch (reg) {
+ case TEGRA210_MIXER_RX1_SOFT_RESET:
+ case TEGRA210_MIXER_RX1_STATUS:
+
+ case TEGRA210_MIXER_TX1_SOFT_RESET:
+ case TEGRA210_MIXER_TX1_STATUS:
+ case TEGRA210_MIXER_TX1_INT_STATUS:
+ case TEGRA210_MIXER_TX1_INT_SET:
+
+ case TEGRA210_MIXER_SOFT_RESET:
+ case TEGRA210_MIXER_STATUS:
+ case TEGRA210_MIXER_INT_STATUS:
+ case TEGRA210_MIXER_GAIN_CFG_RAM_CTRL:
+ case TEGRA210_MIXER_GAIN_CFG_RAM_DATA:
+ case TEGRA210_MIXER_PEAKM_RAM_CTRL:
+ case TEGRA210_MIXER_PEAKM_RAM_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_mixer_precious_reg(struct device *dev,
+ unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_MIXER_GAIN_CFG_RAM_DATA:
+ case TEGRA210_MIXER_PEAKM_RAM_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tegra210_mixer_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA210_MIXER_CTRL,
+ .writeable_reg = tegra210_mixer_wr_reg,
+ .readable_reg = tegra210_mixer_rd_reg,
+ .volatile_reg = tegra210_mixer_volatile_reg,
+ .precious_reg = tegra210_mixer_precious_reg,
+ .reg_defaults = tegra210_mixer_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tegra210_mixer_reg_defaults),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct of_device_id tegra210_mixer_of_match[] = {
+ { .compatible = "nvidia,tegra210-amixer" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra210_mixer_of_match);
+
+static int tegra210_mixer_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tegra210_mixer *mixer;
+ void __iomem *regs;
+ int err, i;
+
+ mixer = devm_kzalloc(dev, sizeof(*mixer), GFP_KERNEL);
+ if (!mixer)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, mixer);
+
+ /* Use default gain value for all MIXER inputs */
+ for (i = 0; i < TEGRA210_MIXER_RX_MAX; i++)
+ mixer->gain_value[i] = gain_params.gain_value;
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ mixer->regmap = devm_regmap_init_mmio(dev, regs,
+ &tegra210_mixer_regmap_config);
+ if (IS_ERR(mixer->regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ return PTR_ERR(mixer->regmap);
+ }
+
+ regcache_cache_only(mixer->regmap, true);
+
+ err = devm_snd_soc_register_component(dev, &tegra210_mixer_cmpnt,
+ tegra210_mixer_dais,
+ ARRAY_SIZE(tegra210_mixer_dais));
+ if (err) {
+ dev_err(dev, "can't register MIXER component, err: %d\n", err);
+ return err;
+ }
+
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static int tegra210_mixer_platform_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra210_mixer_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra210_mixer_runtime_suspend,
+ tegra210_mixer_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver tegra210_mixer_driver = {
+ .driver = {
+ .name = "tegra210_mixer",
+ .of_match_table = tegra210_mixer_of_match,
+ .pm = &tegra210_mixer_pm_ops,
+ },
+ .probe = tegra210_mixer_platform_probe,
+ .remove = tegra210_mixer_platform_remove,
+};
+module_platform_driver(tegra210_mixer_driver);
+
+MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>");
+MODULE_DESCRIPTION("Tegra210 MIXER ASoC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/tegra/tegra210_mixer.h b/sound/soc/tegra/tegra210_mixer.h
new file mode 100644
index 000000000..a330530fb
--- /dev/null
+++ b/sound/soc/tegra/tegra210_mixer.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra210_mixer.h - Definitions for Tegra210 MIXER driver
+ *
+ * Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
+ *
+ */
+
+#ifndef __TEGRA210_MIXER_H__
+#define __TEGRA210_MIXER_H__
+
+/* XBAR_RX related MIXER offsets */
+#define TEGRA210_MIXER_RX1_SOFT_RESET 0x04
+#define TEGRA210_MIXER_RX1_STATUS 0x10
+#define TEGRA210_MIXER_RX1_CIF_CTRL 0x24
+#define TEGRA210_MIXER_RX1_CTRL 0x28
+#define TEGRA210_MIXER_RX1_PEAK_CTRL 0x2c
+#define TEGRA210_MIXER_RX1_SAMPLE_COUNT 0x30
+
+/* XBAR_TX related MIXER offsets */
+#define TEGRA210_MIXER_TX1_ENABLE 0x280
+#define TEGRA210_MIXER_TX1_SOFT_RESET 0x284
+#define TEGRA210_MIXER_TX1_STATUS 0x290
+#define TEGRA210_MIXER_TX1_INT_STATUS 0x294
+#define TEGRA210_MIXER_TX1_INT_MASK 0x298
+#define TEGRA210_MIXER_TX1_INT_SET 0x29c
+#define TEGRA210_MIXER_TX1_INT_CLEAR 0x2a0
+#define TEGRA210_MIXER_TX1_CIF_CTRL 0x2a4
+#define TEGRA210_MIXER_TX1_ADDER_CONFIG 0x2a8
+
+/* MIXER related offsets */
+#define TEGRA210_MIXER_ENABLE 0x400
+#define TEGRA210_MIXER_SOFT_RESET 0x404
+#define TEGRA210_MIXER_CG 0x408
+#define TEGRA210_MIXER_STATUS 0x410
+#define TEGRA210_MIXER_INT_STATUS 0x414
+#define TEGRA210_MIXER_GAIN_CFG_RAM_CTRL 0x42c
+#define TEGRA210_MIXER_GAIN_CFG_RAM_DATA 0x430
+#define TEGRA210_MIXER_PEAKM_RAM_CTRL 0x434
+#define TEGRA210_MIXER_PEAKM_RAM_DATA 0x438
+#define TEGRA210_MIXER_CTRL 0x43c
+
+#define TEGRA210_MIXER_TX2_ADDER_CONFIG (TEGRA210_MIXER_TX1_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE)
+#define TEGRA210_MIXER_TX3_ADDER_CONFIG (TEGRA210_MIXER_TX2_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE)
+#define TEGRA210_MIXER_TX4_ADDER_CONFIG (TEGRA210_MIXER_TX3_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE)
+#define TEGRA210_MIXER_TX5_ADDER_CONFIG (TEGRA210_MIXER_TX4_ADDER_CONFIG + TEGRA210_MIXER_REG_STRIDE)
+
+#define TEGRA210_MIXER_TX2_ENABLE (TEGRA210_MIXER_TX1_ENABLE + TEGRA210_MIXER_REG_STRIDE)
+#define TEGRA210_MIXER_TX3_ENABLE (TEGRA210_MIXER_TX2_ENABLE + TEGRA210_MIXER_REG_STRIDE)
+#define TEGRA210_MIXER_TX4_ENABLE (TEGRA210_MIXER_TX3_ENABLE + TEGRA210_MIXER_REG_STRIDE)
+#define TEGRA210_MIXER_TX5_ENABLE (TEGRA210_MIXER_TX4_ENABLE + TEGRA210_MIXER_REG_STRIDE)
+
+/* Fields in TEGRA210_MIXER_ENABLE */
+#define TEGRA210_MIXER_ENABLE_SHIFT 0
+#define TEGRA210_MIXER_ENABLE_MASK (1 << TEGRA210_MIXER_ENABLE_SHIFT)
+#define TEGRA210_MIXER_EN (1 << TEGRA210_MIXER_ENABLE_SHIFT)
+
+/* Fields in TEGRA210_MIXER_GAIN_CFG_RAM_CTRL */
+#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_0 0x0
+#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_STRIDE 0x10
+
+#define TEGRA210_MIXER_GAIN_CFG_RAM_RW_SHIFT 14
+#define TEGRA210_MIXER_GAIN_CFG_RAM_RW_MASK (1 << TEGRA210_MIXER_GAIN_CFG_RAM_RW_SHIFT)
+#define TEGRA210_MIXER_GAIN_CFG_RAM_RW_WRITE (1 << TEGRA210_MIXER_GAIN_CFG_RAM_RW_SHIFT)
+
+#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_SHIFT 13
+#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_MASK (1 << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_SHIFT)
+#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN (1 << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_INIT_EN_SHIFT)
+
+#define TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_SHIFT 12
+#define TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_MASK (1 << TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_SHIFT)
+#define TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN (1 << TEGRA210_MIXER_GAIN_CFG_RAM_SEQ_ACCESS_EN_SHIFT)
+
+#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_SHIFT 0
+#define TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_MASK (0x1ff << TEGRA210_MIXER_GAIN_CFG_RAM_ADDR_SHIFT)
+
+#define TEGRA210_MIXER_REG_STRIDE 0x40
+#define TEGRA210_MIXER_RX_MAX 10
+#define TEGRA210_MIXER_RX_LIMIT (TEGRA210_MIXER_RX_MAX * TEGRA210_MIXER_REG_STRIDE)
+#define TEGRA210_MIXER_TX_MAX 5
+#define TEGRA210_MIXER_TX_LIMIT (TEGRA210_MIXER_RX_LIMIT + (TEGRA210_MIXER_TX_MAX * TEGRA210_MIXER_REG_STRIDE))
+
+#define REG_CFG_DONE_TRIGGER 0xf
+#define VAL_CFG_DONE_TRIGGER 0x1
+
+#define NUM_GAIN_POLY_COEFFS 9
+#define NUM_DURATION_PARMS 4
+
+struct tegra210_mixer_gain_params {
+ int poly_coeff[NUM_GAIN_POLY_COEFFS];
+ int gain_value;
+ int duration[NUM_DURATION_PARMS];
+};
+
+struct tegra210_mixer {
+ int gain_value[TEGRA210_MIXER_RX_MAX];
+ struct regmap *regmap;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra210_mvc.c b/sound/soc/tegra/tegra210_mvc.c
new file mode 100644
index 000000000..725385e17
--- /dev/null
+++ b/sound/soc/tegra/tegra210_mvc.c
@@ -0,0 +1,778 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// tegra210_mvc.c - Tegra210 MVC driver
+//
+// Copyright (c) 2021 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_mvc.h"
+#include "tegra_cif.h"
+
+static const struct reg_default tegra210_mvc_reg_defaults[] = {
+ { TEGRA210_MVC_RX_INT_MASK, 0x00000001},
+ { TEGRA210_MVC_RX_CIF_CTRL, 0x00007700},
+ { TEGRA210_MVC_TX_INT_MASK, 0x00000001},
+ { TEGRA210_MVC_TX_CIF_CTRL, 0x00007700},
+ { TEGRA210_MVC_CG, 0x1},
+ { TEGRA210_MVC_CTRL, TEGRA210_MVC_CTRL_DEFAULT},
+ { TEGRA210_MVC_INIT_VOL, 0x00800000},
+ { TEGRA210_MVC_TARGET_VOL, 0x00800000},
+ { TEGRA210_MVC_DURATION, 0x000012c0},
+ { TEGRA210_MVC_DURATION_INV, 0x0006d3a0},
+ { TEGRA210_MVC_POLY_N1, 0x0000007d},
+ { TEGRA210_MVC_POLY_N2, 0x00000271},
+ { TEGRA210_MVC_PEAK_CTRL, 0x000012c0},
+ { TEGRA210_MVC_CFG_RAM_CTRL, 0x00004000},
+};
+
+static const struct tegra210_mvc_gain_params gain_params = {
+ .poly_coeff = { 23738319, 659403, -3680,
+ 15546680, 2530732, -120985,
+ 12048422, 5527252, -785042 },
+ .poly_n1 = 16,
+ .poly_n2 = 63,
+ .duration = 150,
+ .duration_inv = 14316558,
+};
+
+static int __maybe_unused tegra210_mvc_runtime_suspend(struct device *dev)
+{
+ struct tegra210_mvc *mvc = dev_get_drvdata(dev);
+
+ regmap_read(mvc->regmap, TEGRA210_MVC_CTRL, &(mvc->ctrl_value));
+
+ regcache_cache_only(mvc->regmap, true);
+ regcache_mark_dirty(mvc->regmap);
+
+ return 0;
+}
+
+static int __maybe_unused tegra210_mvc_runtime_resume(struct device *dev)
+{
+ struct tegra210_mvc *mvc = dev_get_drvdata(dev);
+
+ regcache_cache_only(mvc->regmap, false);
+ regcache_sync(mvc->regmap);
+
+ regmap_write(mvc->regmap, TEGRA210_MVC_CTRL, mvc->ctrl_value);
+ regmap_update_bits(mvc->regmap,
+ TEGRA210_MVC_SWITCH,
+ TEGRA210_MVC_VOLUME_SWITCH_MASK,
+ TEGRA210_MVC_VOLUME_SWITCH_TRIGGER);
+
+ return 0;
+}
+
+static void tegra210_mvc_write_ram(struct regmap *regmap)
+{
+ int i;
+
+ regmap_write(regmap, TEGRA210_MVC_CFG_RAM_CTRL,
+ TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN |
+ TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN |
+ TEGRA210_MVC_CFG_RAM_CTRL_RW_WRITE);
+
+ for (i = 0; i < NUM_GAIN_POLY_COEFFS; i++)
+ regmap_write(regmap, TEGRA210_MVC_CFG_RAM_DATA,
+ gain_params.poly_coeff[i]);
+}
+
+static void tegra210_mvc_conv_vol(struct tegra210_mvc *mvc, u8 chan, s32 val)
+{
+ /*
+ * Volume control read from mixer control is with
+ * 100x scaling; for CURVE_POLY the reg range
+ * is 0-100 (linear, Q24) and for CURVE_LINEAR
+ * it is -120dB to +40dB (Q8)
+ */
+ if (mvc->curve_type == CURVE_POLY) {
+ if (val > 10000)
+ val = 10000;
+ mvc->volume[chan] = ((val * (1<<8)) / 100) << 16;
+ } else {
+ val -= 12000;
+ mvc->volume[chan] = (val * (1<<8)) / 100;
+ }
+}
+
+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);
+ u32 val;
+
+ pm_runtime_get_sync(cmpnt->dev);
+ regmap_read(mvc->regmap, TEGRA210_MVC_CTRL, &val);
+ pm_runtime_put(cmpnt->dev);
+
+ 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);
+
+ /*
+ * 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_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 tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
+ u32 value;
+ int err;
+
+ err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SWITCH,
+ value, !(value & TEGRA210_MVC_VOLUME_SWITCH_MASK),
+ 10, 10000);
+ if (err < 0)
+ dev_err(cmpnt->dev,
+ "Volume switch trigger is still active, err = %d\n",
+ err);
+
+ return err;
+}
+
+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;
+
+ 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;
+ }
+
+ regmap_update_bits_check(mvc->regmap, TEGRA210_MVC_CTRL,
+ TEGRA210_MVC_MUTE_MASK,
+ mute_val << TEGRA210_MVC_MUTE_SHIFT,
+ &change);
+
+ 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);
+
+ 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,
+ 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_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
+ u8 chan = TEGRA210_MVC_GET_CHAN(mc->reg, TEGRA210_MVC_TARGET_VOL);
+ s32 val = mvc->volume[chan];
+
+ if (mvc->curve_type == CURVE_POLY) {
+ val = ((val >> 16) * 100) >> 8;
+ } else {
+ val = (val * 100) >> 8;
+ val += 12000;
+ }
+
+ ucontrol->value.integer.value[0] = val;
+
+ return 0;
+}
+
+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);
+ 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);
+
+ err = tegra210_mvc_volume_switch_timeout(cmpnt);
+ if (err < 0)
+ goto end;
+
+ 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, mc->reg, mvc->volume[chan]);
+
+ regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH,
+ TEGRA210_MVC_VOLUME_SWITCH_MASK,
+ TEGRA210_MVC_VOLUME_SWITCH_TRIGGER);
+
+ err = 1;
+
+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)
+{
+ int i;
+
+ /* Change volume to default init for new curve type */
+ if (mvc->curve_type == CURVE_POLY) {
+ for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++)
+ mvc->volume[i] = TEGRA210_MVC_INIT_VOL_DEFAULT_POLY;
+ } else {
+ for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++)
+ mvc->volume[i] = TEGRA210_MVC_INIT_VOL_DEFAULT_LINEAR;
+ }
+
+ pm_runtime_get_sync(dev);
+
+ /* Program curve type */
+ regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL,
+ TEGRA210_MVC_CURVE_TYPE_MASK,
+ mvc->curve_type <<
+ TEGRA210_MVC_CURVE_TYPE_SHIFT);
+
+ /* Init volume for all channels */
+ for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++) {
+ regmap_write(mvc->regmap,
+ TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_INIT_VOL, i),
+ mvc->volume[i]);
+ regmap_write(mvc->regmap,
+ TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_TARGET_VOL, i),
+ mvc->volume[i]);
+ }
+
+ /* Trigger volume switch */
+ regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH,
+ TEGRA210_MVC_VOLUME_SWITCH_MASK,
+ TEGRA210_MVC_VOLUME_SWITCH_TRIGGER);
+
+ pm_runtime_put(dev);
+}
+
+static int tegra210_mvc_get_curve_type(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
+
+ ucontrol->value.enumerated.item[0] = mvc->curve_type;
+
+ return 0;
+}
+
+static int tegra210_mvc_put_curve_type(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(cmpnt);
+ unsigned int value;
+
+ regmap_read(mvc->regmap, TEGRA210_MVC_ENABLE, &value);
+ if (value & TEGRA210_MVC_EN) {
+ dev_err(cmpnt->dev,
+ "Curve type can't be set when MVC is running\n");
+ return -EINVAL;
+ }
+
+ if (mvc->curve_type == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ mvc->curve_type = ucontrol->value.enumerated.item[0];
+
+ tegra210_mvc_reset_vol_settings(mvc, cmpnt->dev);
+
+ return 1;
+}
+
+static int tegra210_mvc_set_audio_cif(struct tegra210_mvc *mvc,
+ struct snd_pcm_hw_params *params,
+ unsigned int reg)
+{
+ unsigned 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_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(mvc->regmap, reg, &cif_conf);
+
+ return 0;
+}
+
+static int tegra210_mvc_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_mvc *mvc = snd_soc_dai_get_drvdata(dai);
+ int err, val;
+
+ /*
+ * Soft Reset: Below performs module soft reset which clears
+ * all FSM logic, flushes flow control of FIFO and resets the
+ * state register. It also brings module back to disabled
+ * state (without flushing the data in the pipe).
+ */
+ regmap_write(mvc->regmap, TEGRA210_MVC_SOFT_RESET, 1);
+
+ err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SOFT_RESET,
+ val, !val, 10, 10000);
+ if (err < 0) {
+ dev_err(dev, "SW reset failed, err = %d\n", err);
+ return err;
+ }
+
+ /* Set RX CIF */
+ err = tegra210_mvc_set_audio_cif(mvc, params, TEGRA210_MVC_RX_CIF_CTRL);
+ if (err) {
+ dev_err(dev, "Can't set MVC RX CIF: %d\n", err);
+ return err;
+ }
+
+ /* Set TX CIF */
+ err = tegra210_mvc_set_audio_cif(mvc, params, TEGRA210_MVC_TX_CIF_CTRL);
+ if (err) {
+ dev_err(dev, "Can't set MVC TX CIF: %d\n", err);
+ return err;
+ }
+
+ tegra210_mvc_write_ram(mvc->regmap);
+
+ /* Program poly_n1, poly_n2, duration */
+ regmap_write(mvc->regmap, TEGRA210_MVC_POLY_N1, gain_params.poly_n1);
+ regmap_write(mvc->regmap, TEGRA210_MVC_POLY_N2, gain_params.poly_n2);
+ regmap_write(mvc->regmap, TEGRA210_MVC_DURATION, gain_params.duration);
+
+ /* Program duration_inv */
+ regmap_write(mvc->regmap, TEGRA210_MVC_DURATION_INV,
+ gain_params.duration_inv);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops tegra210_mvc_dai_ops = {
+ .hw_params = tegra210_mvc_hw_params,
+};
+
+static const char * const tegra210_mvc_curve_type_text[] = {
+ "Poly",
+ "Linear",
+};
+
+static const struct soc_enum tegra210_mvc_curve_type_ctrl =
+ SOC_ENUM_SINGLE_EXT(2, tegra210_mvc_curve_type_text);
+
+#define TEGRA210_MVC_VOL_CTRL(chan) \
+ SOC_SINGLE_EXT("Channel" #chan " Volume", \
+ TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_TARGET_VOL, \
+ (chan - 1)), \
+ 0, 16000, 0, tegra210_mvc_get_vol, \
+ tegra210_mvc_put_vol)
+
+static const struct snd_kcontrol_new tegra210_mvc_vol_ctrl[] = {
+ /* Per channel volume control */
+ TEGRA210_MVC_VOL_CTRL(1),
+ TEGRA210_MVC_VOL_CTRL(2),
+ TEGRA210_MVC_VOL_CTRL(3),
+ TEGRA210_MVC_VOL_CTRL(4),
+ TEGRA210_MVC_VOL_CTRL(5),
+ TEGRA210_MVC_VOL_CTRL(6),
+ TEGRA210_MVC_VOL_CTRL(7),
+ TEGRA210_MVC_VOL_CTRL(8),
+
+ /* Per channel mute */
+ SOC_SINGLE_EXT("Per Chan Mute Mask",
+ 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),
+};
+
+static struct snd_soc_dai_driver tegra210_mvc_dais[] = {
+ /* Input */
+ {
+ .name = "MVC-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,
+ },
+ },
+
+ /* Output */
+ {
+ .name = "MVC-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_mvc_dai_ops,
+ }
+};
+
+static const struct snd_soc_dapm_widget tegra210_mvc_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX", NULL, 0, TEGRA210_MVC_ENABLE,
+ TEGRA210_MVC_EN_SHIFT, 0),
+};
+
+#define MVC_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_mvc_routes[] = {
+ { "TX", NULL, "RX" },
+ MVC_ROUTES("Playback"),
+ MVC_ROUTES("Capture"),
+};
+
+static const struct snd_soc_component_driver tegra210_mvc_cmpnt = {
+ .dapm_widgets = tegra210_mvc_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra210_mvc_widgets),
+ .dapm_routes = tegra210_mvc_routes,
+ .num_dapm_routes = ARRAY_SIZE(tegra210_mvc_routes),
+ .controls = tegra210_mvc_vol_ctrl,
+ .num_controls = ARRAY_SIZE(tegra210_mvc_vol_ctrl),
+};
+
+static bool tegra210_mvc_rd_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_MVC_RX_STATUS ... TEGRA210_MVC_CONFIG_ERR_TYPE:
+ return true;
+ default:
+ return false;
+ };
+}
+
+static bool tegra210_mvc_wr_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_MVC_RX_INT_MASK ... TEGRA210_MVC_RX_CIF_CTRL:
+ case TEGRA210_MVC_TX_INT_MASK ... TEGRA210_MVC_TX_CIF_CTRL:
+ case TEGRA210_MVC_ENABLE ... TEGRA210_MVC_CG:
+ case TEGRA210_MVC_CTRL ... TEGRA210_MVC_CFG_RAM_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_mvc_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_MVC_RX_STATUS:
+ case TEGRA210_MVC_RX_INT_STATUS:
+ case TEGRA210_MVC_RX_INT_SET:
+
+ case TEGRA210_MVC_TX_STATUS:
+ case TEGRA210_MVC_TX_INT_STATUS:
+ case TEGRA210_MVC_TX_INT_SET:
+
+ case TEGRA210_MVC_SOFT_RESET:
+ case TEGRA210_MVC_STATUS:
+ case TEGRA210_MVC_INT_STATUS:
+ case TEGRA210_MVC_SWITCH:
+ case TEGRA210_MVC_CFG_RAM_CTRL:
+ case TEGRA210_MVC_CFG_RAM_DATA:
+ case TEGRA210_MVC_PEAK_VALUE:
+ case TEGRA210_MVC_CTRL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tegra210_mvc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA210_MVC_CONFIG_ERR_TYPE,
+ .writeable_reg = tegra210_mvc_wr_reg,
+ .readable_reg = tegra210_mvc_rd_reg,
+ .volatile_reg = tegra210_mvc_volatile_reg,
+ .reg_defaults = tegra210_mvc_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tegra210_mvc_reg_defaults),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct of_device_id tegra210_mvc_of_match[] = {
+ { .compatible = "nvidia,tegra210-mvc" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra210_mvc_of_match);
+
+static int tegra210_mvc_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tegra210_mvc *mvc;
+ void __iomem *regs;
+ int err;
+
+ mvc = devm_kzalloc(dev, sizeof(*mvc), GFP_KERNEL);
+ if (!mvc)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, mvc);
+
+ mvc->curve_type = CURVE_LINEAR;
+ mvc->ctrl_value = TEGRA210_MVC_CTRL_DEFAULT;
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ mvc->regmap = devm_regmap_init_mmio(dev, regs,
+ &tegra210_mvc_regmap_config);
+ if (IS_ERR(mvc->regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ return PTR_ERR(mvc->regmap);
+ }
+
+ regcache_cache_only(mvc->regmap, true);
+
+ err = devm_snd_soc_register_component(dev, &tegra210_mvc_cmpnt,
+ tegra210_mvc_dais,
+ ARRAY_SIZE(tegra210_mvc_dais));
+ if (err) {
+ dev_err(dev, "can't register MVC component, err: %d\n", err);
+ return err;
+ }
+
+ pm_runtime_enable(dev);
+
+ tegra210_mvc_reset_vol_settings(mvc, &pdev->dev);
+
+ return 0;
+}
+
+static int tegra210_mvc_platform_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra210_mvc_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra210_mvc_runtime_suspend,
+ tegra210_mvc_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver tegra210_mvc_driver = {
+ .driver = {
+ .name = "tegra210-mvc",
+ .of_match_table = tegra210_mvc_of_match,
+ .pm = &tegra210_mvc_pm_ops,
+ },
+ .probe = tegra210_mvc_platform_probe,
+ .remove = tegra210_mvc_platform_remove,
+};
+module_platform_driver(tegra210_mvc_driver)
+
+MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>");
+MODULE_DESCRIPTION("Tegra210 MVC ASoC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/tegra/tegra210_mvc.h b/sound/soc/tegra/tegra210_mvc.h
new file mode 100644
index 000000000..d775335dc
--- /dev/null
+++ b/sound/soc/tegra/tegra210_mvc.h
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra210_mvc.h - Definitions for Tegra210 MVC driver
+ *
+ * Copyright (c) 2021 NVIDIA CORPORATION. All rights reserved.
+ *
+ */
+
+#ifndef __TEGRA210_MVC_H__
+#define __TEGRA210_MVC_H__
+
+/*
+ * MVC_RX registers are with respect to XBAR.
+ * The data comes from XBAR to MVC.
+ */
+#define TEGRA210_MVC_RX_STATUS 0x0c
+#define TEGRA210_MVC_RX_INT_STATUS 0x10
+#define TEGRA210_MVC_RX_INT_MASK 0x14
+#define TEGRA210_MVC_RX_INT_SET 0x18
+#define TEGRA210_MVC_RX_INT_CLEAR 0x1c
+#define TEGRA210_MVC_RX_CIF_CTRL 0x20
+
+/*
+ * MVC_TX registers are with respect to XBAR.
+ * The data goes out of MVC.
+ */
+#define TEGRA210_MVC_TX_STATUS 0x4c
+#define TEGRA210_MVC_TX_INT_STATUS 0x50
+#define TEGRA210_MVC_TX_INT_MASK 0x54
+#define TEGRA210_MVC_TX_INT_SET 0x58
+#define TEGRA210_MVC_TX_INT_CLEAR 0x5c
+#define TEGRA210_MVC_TX_CIF_CTRL 0x60
+
+/* Register offsets from TEGRA210_MVC*_BASE */
+#define TEGRA210_MVC_ENABLE 0x80
+#define TEGRA210_MVC_SOFT_RESET 0x84
+#define TEGRA210_MVC_CG 0x88
+#define TEGRA210_MVC_STATUS 0x90
+#define TEGRA210_MVC_INT_STATUS 0x94
+#define TEGRA210_MVC_CTRL 0xa8
+#define TEGRA210_MVC_SWITCH 0xac
+#define TEGRA210_MVC_INIT_VOL 0xb0
+#define TEGRA210_MVC_TARGET_VOL 0xd0
+#define TEGRA210_MVC_DURATION 0xf0
+#define TEGRA210_MVC_DURATION_INV 0xf4
+#define TEGRA210_MVC_POLY_N1 0xf8
+#define TEGRA210_MVC_POLY_N2 0xfc
+#define TEGRA210_MVC_PEAK_CTRL 0x100
+#define TEGRA210_MVC_CFG_RAM_CTRL 0x104
+#define TEGRA210_MVC_CFG_RAM_DATA 0x108
+#define TEGRA210_MVC_PEAK_VALUE 0x10c
+#define TEGRA210_MVC_CONFIG_ERR_TYPE 0x12c
+
+/* Fields in TEGRA210_MVC_ENABLE */
+#define TEGRA210_MVC_EN_SHIFT 0
+#define TEGRA210_MVC_EN (1 << TEGRA210_MVC_EN_SHIFT)
+
+#define TEGRA210_MVC_MUTE_SHIFT 8
+#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)
+#define TEGRA210_MVC_PER_CHAN_CTRL_EN (1 << TEGRA210_MVC_PER_CHAN_CTRL_EN_SHIFT)
+
+#define TEGRA210_MVC_CURVE_TYPE_SHIFT 1
+#define TEGRA210_MVC_CURVE_TYPE_MASK (1 << TEGRA210_MVC_CURVE_TYPE_SHIFT)
+
+#define TEGRA210_MVC_VOLUME_SWITCH_SHIFT 2
+#define TEGRA210_MVC_VOLUME_SWITCH_MASK (1 << TEGRA210_MVC_VOLUME_SWITCH_SHIFT)
+#define TEGRA210_MVC_VOLUME_SWITCH_TRIGGER (1 << TEGRA210_MVC_VOLUME_SWITCH_SHIFT)
+#define TEGRA210_MVC_CTRL_DEFAULT 0x40000003
+
+#define TEGRA210_MVC_INIT_VOL_DEFAULT_POLY 0x01000000
+#define TEGRA210_MVC_INIT_VOL_DEFAULT_LINEAR 0x00000000
+
+/* Fields in TEGRA210_MVC ram ctrl */
+#define TEGRA210_MVC_CFG_RAM_CTRL_RW_SHIFT 14
+#define TEGRA210_MVC_CFG_RAM_CTRL_RW_WRITE (1 << TEGRA210_MVC_CFG_RAM_CTRL_RW_SHIFT)
+
+#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT 13
+#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN (1 << TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN_SHIFT)
+
+#define TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT 12
+#define TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN (1 << TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN_SHIFT)
+
+#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_SHIFT 0
+#define TEGRA210_MVC_CFG_RAM_CTRL_ADDR_MASK (0x1ff << TEGRA210_MVC_CFG_RAM_CTRL_ADDR_SHIFT)
+
+#define REG_SIZE 4
+#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 {
+ CURVE_POLY,
+ CURVE_LINEAR,
+};
+
+struct tegra210_mvc_gain_params {
+ int poly_coeff[NUM_GAIN_POLY_COEFFS];
+ int poly_n1;
+ int poly_n2;
+ int duration;
+ int duration_inv;
+};
+
+struct tegra210_mvc {
+ int volume[TEGRA210_MVC_MAX_CHAN_COUNT];
+ unsigned int curve_type;
+ unsigned int ctrl_value;
+ struct regmap *regmap;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra210_ope.c b/sound/soc/tegra/tegra210_ope.c
new file mode 100644
index 000000000..3dd2bdec6
--- /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 000000000..2835af6ce
--- /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 000000000..205d956ab
--- /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 000000000..6d3de4ff0
--- /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/tegra210_sfc.c b/sound/soc/tegra/tegra210_sfc.c
new file mode 100644
index 000000000..5d2125aa6
--- /dev/null
+++ b/sound/soc/tegra/tegra210_sfc.c
@@ -0,0 +1,3643 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// tegra210_sfc.c - Tegra210 SFC driver
+//
+// Copyright (c) 2021-2023 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_sfc.h"
+#include "tegra_cif.h"
+
+#define UNSUPP_CONV ((void *)(-EOPNOTSUPP))
+#define BYPASS_CONV NULL
+
+static const struct reg_default tegra210_sfc_reg_defaults[] = {
+ { TEGRA210_SFC_RX_INT_MASK, 0x00000001},
+ { TEGRA210_SFC_RX_CIF_CTRL, 0x00007700},
+ { TEGRA210_SFC_TX_INT_MASK, 0x00000001},
+ { TEGRA210_SFC_TX_CIF_CTRL, 0x00007700},
+ { TEGRA210_SFC_CG, 0x1},
+ { TEGRA210_SFC_CFG_RAM_CTRL, 0x00004000},
+};
+
+static const int tegra210_sfc_rates[TEGRA210_SFC_NUM_RATES] = {
+ 8000,
+ 11025,
+ 16000,
+ 22050,
+ 24000,
+ 32000,
+ 44100,
+ 48000,
+ 64000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+};
+
+/* coeff RAM tables required for SFC */
+static u32 coef_8to11[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x0018a102,//header
+ 0x000005d6,//input gain
+ 0x00c6543e, 0xff342935, 0x0052f116,
+ 0x000a1d78, 0xff3330c0, 0x005f88a3,
+ 0xffbee7c0, 0xff2b5ba5, 0x0073eb26,
+ 0x00000003,//output gain
+ 0x00235204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0000015f,//input gain
+ 0x00a7909c, 0xff241c71, 0x005f5e00,
+ 0xffca77f4, 0xff20dd50, 0x006855eb,
+ 0xff86c552, 0xff18137a, 0x00773648,
+ 0x00000001//output gain
+};
+
+static u32 coef_8to16[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00006102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002//output gain
+};
+
+static u32 coef_8to22[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x0018a102,//header
+ 0x000005d6,//input gain
+ 0x00c6543e, 0xff342935, 0x0052f116,
+ 0x000a1d78, 0xff3330c0, 0x005f88a3,
+ 0xffbee7c0, 0xff2b5ba5, 0x0073eb26,
+ 0x00000003,//output gain
+ 0x00230204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x000005f3,//input gain
+ 0x00d816d6, 0xff385383, 0x004fe566,
+ 0x003c548d, 0xff38c23d, 0x005d0b1c,
+ 0xfff02f7d, 0xff31e983, 0x0072d65d,
+ 0x00000001//output gain
+};
+
+static u32 coef_8to24[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x0000a105,//header
+ 0x000005e1,//input gain
+ 0x00dca92f, 0xff45647a, 0x0046b59c,
+ 0x00429d1e, 0xff4fec62, 0x00516d30,
+ 0xffdea779, 0xff5e08ba, 0x0060185e,
+ 0xffafbab2, 0xff698d5a, 0x006ce3ae,
+ 0xff9a82d2, 0xff704674, 0x007633c5,
+ 0xff923433, 0xff721128, 0x007cff42,
+ 0x00000003//output gain
+};
+
+static u32 coef_8to32[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00006102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002//output gain
+};
+
+static u32 coef_8to44[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x0156105,//interpolation + IIR filter
+ 0x0000d649,//input gain
+ 0x00e87afb, 0xff5f69d0, 0x003df3cf,
+ 0x007ce488, 0xff99a5c8, 0x0056a6a0,
+ 0x00344928, 0xffcba3e5, 0x006be470,
+ 0x00137aa7, 0xffe60276, 0x00773410,
+ 0x0005fa2a, 0xfff1ac11, 0x007c795b,
+ 0x00012d36, 0xfff5eca2, 0x007f10ef,
+ 0x00000002,//ouptut gain
+ 0x0021a102,//interpolation + IIR filter
+ 0x00000e00,//input gain
+ 0x00e2e000, 0xff6e1a00, 0x002aaa00,
+ 0x00610a00, 0xff5dda00, 0x003ccc00,
+ 0x00163a00, 0xff3c0400, 0x00633200,
+ 0x00000003,//Output gain
+ 0x00000204,//Farrow filter
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_8to48[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00156105,//interpolation + IIR Filter
+ 0x0000d649,//input gain
+ 0x00e87afb, 0xff5f69d0, 0x003df3cf,
+ 0x007ce488, 0xff99a5c8, 0x0056a6a0,
+ 0x00344928, 0xffcba3e5, 0x006be470,
+ 0x00137aa7, 0xffe60276, 0x00773410,
+ 0x0005fa2a, 0xfff1ac11, 0x007c795b,
+ 0x00012d36, 0xfff5eca2, 0x007f10ef,
+ 0x00000002,//ouptut gain
+ 0x0000a102,//interpolation + IIR filter
+ 0x00000e00,//input gain
+ 0x00e2e000, 0xff6e1a00, 0x002aaa00,
+ 0x00610a00, 0xff5dda00, 0x003ccc00,
+ 0x00163a00, 0xff3c0400, 0x00633200,
+ 0x00000003//output gain
+};
+
+static u32 coef_8to88[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x0024a102,//header
+ 0x0000007d,//input gain
+ 0x007d1f20, 0xff1a540e, 0x00678bf9,
+ 0xff916625, 0xff16b0ff, 0x006e433a,
+ 0xff5af660, 0xff0eb91f, 0x00797356,
+ 0x00000003,//output gain
+ 0x00000204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_8to96[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x0000a102,//header
+ 0x0000007d,//input gain
+ 0x007d1f20, 0xff1a540e, 0x00678bf9,
+ 0xff916625, 0xff16b0ff, 0x006e433a,
+ 0xff5af660, 0xff0eb91f, 0x00797356,
+ 0x00000003//output gain
+};
+
+static u32 coef_11to8[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0000015f,//input gain
+ 0x00a7909c, 0xff241c71, 0x005f5e00,
+ 0xffca77f4, 0xff20dd50, 0x006855eb,
+ 0xff86c552, 0xff18137a, 0x00773648,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000005f3,//input gain
+ 0x00d816d6, 0xff385383, 0x004fe566,
+ 0x003c548d, 0xff38c23d, 0x005d0b1c,
+ 0xfff02f7d, 0xff31e983, 0x0072d65d,
+ 0x00000002,//output gain
+ 0x00239204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_11to16[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00009204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_11to22[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00006102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002//output gain
+};
+
+static u32 coef_11to24[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00005204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_11to32[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00246102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x00009204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_11to44[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00006102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002//output gain
+};
+
+static u32 coef_11to48[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00246102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x00005204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_11to88[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00006102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002//output gain
+};
+
+static u32 coef_11to96[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00246102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x00000204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_16to8[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_16to11[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000fa103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000003,//output gain
+ 0x001a5204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_16to22[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x0018a102,//header
+ 0x000005d6,//input gain
+ 0x00c6543e, 0xff342935, 0x0052f116,
+ 0x000a1d78, 0xff3330c0, 0x005f88a3,
+ 0xffbee7c0, 0xff2b5ba5, 0x0073eb26,
+ 0x00000003,//output gain
+ 0x00235204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0000015f,//input gain
+ 0x00a7909c, 0xff241c71, 0x005f5e00,
+ 0xffca77f4, 0xff20dd50, 0x006855eb,
+ 0xff86c552, 0xff18137a, 0x00773648,
+ 0x00000001//output gain
+};
+
+static u32 coef_16to24[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x0015a105,//header
+ 0x00000292,//input gain
+ 0x00e4320a, 0xff41d2d9, 0x004911ac,
+ 0x005dd9e3, 0xff4c7d80, 0x0052103e,
+ 0xfff8ebef, 0xff5b6fab, 0x005f0a0d,
+ 0xffc4b414, 0xff68582c, 0x006b38e5,
+ 0xffabb861, 0xff704bec, 0x0074de52,
+ 0xffa19f4c, 0xff729059, 0x007c7e90,
+ 0x00000003,//output gain
+ 0x00005105,//header
+ 0x00000292,//input gain
+ 0x00e4320a, 0xff41d2d9, 0x004911ac,
+ 0x005dd9e3, 0xff4c7d80, 0x0052103e,
+ 0xfff8ebef, 0xff5b6fab, 0x005f0a0d,
+ 0xffc4b414, 0xff68582c, 0x006b38e5,
+ 0xffabb861, 0xff704bec, 0x0074de52,
+ 0xffa19f4c, 0xff729059, 0x007c7e90,
+ 0x00000001//output gain
+};
+
+static u32 coef_16to32[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00006102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002//output gain
+};
+
+static u32 coef_16to44[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00156105,//interpolation + IIR filter
+ 0x0000d649,//input gain
+ 0x00e87afb, 0xff5f69d0, 0x003df3cf,
+ 0x007ce488, 0xff99a5c8, 0x0056a6a0,
+ 0x00344928, 0xffcba3e5, 0x006be470,
+ 0x00137aa7, 0xffe60276, 0x00773410,
+ 0x0005fa2a, 0xfff1ac11, 0x007c795b,
+ 0x00012d36, 0xfff5eca2, 0x007f10ef,
+ 0x00000002,//output gain
+ 0x0021a102,//interpolation + IIR filter
+ 0x00000e00,//input gain
+ 0x00e2e000, 0xff6e1a00, 0x002aaa00,
+ 0x00610a00, 0xff5dda00, 0x003ccc00,
+ 0x00163a00, 0xff3c0400, 0x00633200,
+ 0x00000003,//output gain
+ 0x002c0204,//Farrow Filter
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005101,//IIR Filter + Decimator
+ 0x0000203c,//input gain
+ 0x00f52d35, 0xff2e2162, 0x005a21e0,
+ 0x00c6f0f0, 0xff2ecd69, 0x006fa78d,
+ 0x00000001//output gain
+};
+
+static u32 coef_16to48[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x0000a105,//interpolation + IIR Filter
+ 0x00000784,//input gain
+ 0x00cc516e, 0xff2c9639, 0x005ad5b3,
+ 0x0013ad0d, 0xff3d4799, 0x0063ce75,
+ 0xffb6f398, 0xff5138d1, 0x006e9e1f,
+ 0xff9186e5, 0xff5f96a4, 0x0076a86e,
+ 0xff82089c, 0xff676b81, 0x007b9f8a,
+ 0xff7c48a5, 0xff6a31e7, 0x007ebb7b,
+ 0x00000003//output gain
+};
+
+static u32 coef_16to88[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x0018a102,//header
+ 0x000005d6,//input gain
+ 0x00c6543e, 0xff342935, 0x0052f116,
+ 0x000a1d78, 0xff3330c0, 0x005f88a3,
+ 0xffbee7c0, 0xff2b5ba5, 0x0073eb26,
+ 0x00000003,//output gain
+ 0x00000204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_16to96[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x0000a102,//header
+ 0x000005d6,//input gain
+ 0x00c6543e, 0xff342935, 0x0052f116,
+ 0x000a1d78, 0xff3330c0, 0x005f88a3,
+ 0xffbee7c0, 0xff2b5ba5, 0x0073eb26,
+ 0x00000003//output gain
+};
+
+static u32 coef_16to176[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x0024a102,//header
+ 0x0000007d,//input gain
+ 0x007d1f20, 0xff1a540e, 0x00678bf9,
+ 0xff916625, 0xff16b0ff, 0x006e433a,
+ 0xff5af660, 0xff0eb91f, 0x00797356,
+ 0x00000003,//output gain
+ 0x00000204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_16to192[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x0000a102,//header
+ 0x0000007d,//input gain
+ 0x007d1f20, 0xff1a540e, 0x00678bf9,
+ 0xff916625, 0xff16b0ff, 0x006e433a,
+ 0xff5af660, 0xff0eb91f, 0x00797356,
+ 0x00000003//output gain
+};
+
+static u32 coef_22to8[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x000005f3,//input gain
+ 0x00d816d6, 0xff385383, 0x004fe566,
+ 0x003c548d, 0xff38c23d, 0x005d0b1c,
+ 0xfff02f7d, 0xff31e983, 0x0072d65d,
+ 0x00000002,//output gain
+ 0x00179204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_22to11[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_22to16[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0000015f,//input gain
+ 0x00a7909c, 0xff241c71, 0x005f5e00,
+ 0xffca77f4, 0xff20dd50, 0x006855eb,
+ 0xff86c552, 0xff18137a, 0x00773648,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000005f3,//input gain
+ 0x00d816d6, 0xff385383, 0x004fe566,
+ 0x003c548d, 0xff38c23d, 0x005d0b1c,
+ 0xfff02f7d, 0xff31e983, 0x0072d65d,
+ 0x00000002,//output gain
+ 0x00239204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_22to24[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00235204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d029,//input gain
+ 0x00f2a98b, 0xff92aa71, 0x001fcd16,
+ 0x00ae9004, 0xffb85140, 0x0041813a,
+ 0x007f8ed1, 0xffd585fc, 0x006a69e6,
+ 0x00000001//output gain
+};
+
+static u32 coef_22to32[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00009204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_22to44[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00006102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002//output gain
+};
+
+static u32 coef_22to48[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00005204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_22to88[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00006102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002//output gain
+};
+
+static u32 coef_22to96[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00246102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x00005204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_22to176[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00006102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002//output gain
+};
+
+static u32 coef_22to192[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00246102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x00000204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_24to8[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00009105,//header
+ 0x000005e1,//input gain
+ 0x00dca92f, 0xff45647a, 0x0046b59c,
+ 0x00429d1e, 0xff4fec62, 0x00516d30,
+ 0xffdea779, 0xff5e08ba, 0x0060185e,
+ 0xffafbab2, 0xff698d5a, 0x006ce3ae,
+ 0xff9a82d2, 0xff704674, 0x007633c5,
+ 0xff923433, 0xff721128, 0x007cff42,
+ 0x00000001//output gain
+};
+
+static u32 coef_24to11[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000f6103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000002,//output gain
+ 0x001a5204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_24to16[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00156105,//header
+ 0x00000292,//input gain
+ 0x00e4320a, 0xff41d2d9, 0x004911ac,
+ 0x005dd9e3, 0xff4c7d80, 0x0052103e,
+ 0xfff8ebef, 0xff5b6fab, 0x005f0a0d,
+ 0xffc4b414, 0xff68582c, 0x006b38e5,
+ 0xffabb861, 0xff704bec, 0x0074de52,
+ 0xffa19f4c, 0xff729059, 0x007c7e90,
+ 0x00000002,//output gain
+ 0x00009105,//header
+ 0x00000292,//input gain
+ 0x00e4320a, 0xff41d2d9, 0x004911ac,
+ 0x005dd9e3, 0xff4c7d80, 0x0052103e,
+ 0xfff8ebef, 0xff5b6fab, 0x005f0a0d,
+ 0xffc4b414, 0xff68582c, 0x006b38e5,
+ 0xffabb861, 0xff704bec, 0x0074de52,
+ 0xffa19f4c, 0xff729059, 0x007c7e90,
+ 0x00000001//output gain
+};
+
+static u32 coef_24to22[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d029,//input gain
+ 0x00f2a98b, 0xff92aa71, 0x001fcd16,
+ 0x00ae9004, 0xffb85140, 0x0041813a,
+ 0x007f8ed1, 0xffd585fc, 0x006a69e6,
+ 0x00000002,//output gain
+ 0x001b6103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000002,//output gain
+ 0x00265204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_24to32[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00009102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001//output gain
+};
+
+static u32 coef_24to44[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00230204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x00001685,//input gain
+ 0x00f53ae9, 0xff52f196, 0x003e3e08,
+ 0x00b9f857, 0xff5d8985, 0x0050070a,
+ 0x008c3e86, 0xff6053f0, 0x006d98ef,
+ 0x00000001//output gain
+};
+
+static u32 coef_24to48[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00006102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002//output gain
+};
+
+static u32 coef_24to88[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00246102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x002f0204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x00000138,//input gain
+ 0x00d5d232, 0xff2a3bf8, 0x005a785c,
+ 0x0034001b, 0xff283109, 0x006462a6,
+ 0xffe6746a, 0xff1fb09c, 0x00758a91,
+ 0x00000001//output gain
+};
+
+static u32 coef_24to96[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00006102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002//output gain
+};
+
+static u32 coef_24to176[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00246102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x00000204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_24to192[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00006102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002//output gain
+};
+
+static u32 coef_32to8[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c5102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_32to11[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000ca102,//header
+ 0x000000af,//input gain
+ 0x00c65663, 0xff23d2ce, 0x005f97d6,
+ 0x00086ad6, 0xff20ec4f, 0x00683201,
+ 0xffbbbef6, 0xff184447, 0x00770963,
+ 0x00000003,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x0000d102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001//output gain
+};
+
+static u32 coef_32to16[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_32to22[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000fa103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000003,//output gain
+ 0x001a5204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_32to24[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000ca102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000003,//output gain
+ 0x0000d102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001//output gain
+};
+
+static u32 coef_32to44[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x0018a102,//header
+ 0x000005d6,//input gain
+ 0x00c6543e, 0xff342935, 0x0052f116,
+ 0x000a1d78, 0xff3330c0, 0x005f88a3,
+ 0xffbee7c0, 0xff2b5ba5, 0x0073eb26,
+ 0x00000003,//output gain
+ 0x00235204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0000015f,//input gain
+ 0x00a7909c, 0xff241c71, 0x005f5e00,
+ 0xffca77f4, 0xff20dd50, 0x006855eb,
+ 0xff86c552, 0xff18137a, 0x00773648,
+ 0x00000001//output gain
+};
+
+static u32 coef_32to48[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x0015a105,//header
+ 0x00000292,//input gain
+ 0x00e4320a, 0xff41d2d9, 0x004911ac,
+ 0x005dd9e3, 0xff4c7d80, 0x0052103e,
+ 0xfff8ebef, 0xff5b6fab, 0x005f0a0d,
+ 0xffc4b414, 0xff68582c, 0x006b38e5,
+ 0xffabb861, 0xff704bec, 0x0074de52,
+ 0xffa19f4c, 0xff729059, 0x007c7e90,
+ 0x00000003,//output gain
+ 0x00005105,//header
+ 0x00000292,//input gain
+ 0x00e4320a, 0xff41d2d9, 0x004911ac,
+ 0x005dd9e3, 0xff4c7d80, 0x0052103e,
+ 0xfff8ebef, 0xff5b6fab, 0x005f0a0d,
+ 0xffc4b414, 0xff68582c, 0x006b38e5,
+ 0xffabb861, 0xff704bec, 0x0074de52,
+ 0xffa19f4c, 0xff729059, 0x007c7e90,
+ 0x00000001//output gain
+};
+
+static u32 coef_32to88[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x0018a102,//header
+ 0x000005d6,//input gain
+ 0x00c6543e, 0xff342935, 0x0052f116,
+ 0x000a1d78, 0xff3330c0, 0x005f88a3,
+ 0xffbee7c0, 0xff2b5ba5, 0x0073eb26,
+ 0x00000003,//output gain
+ 0x00230204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x000005f3,//input gain
+ 0x00d816d6, 0xff385383, 0x004fe566,
+ 0x003c548d, 0xff38c23d, 0x005d0b1c,
+ 0xfff02f7d, 0xff31e983, 0x0072d65d,
+ 0x00000001//output gain
+};
+
+static u32 coef_32to96[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x0000a105,//header
+ 0x00000292,//input gain
+ 0x00e4320a, 0xff41d2d9, 0x004911ac,
+ 0x005dd9e3, 0xff4c7d80, 0x0052103e,
+ 0xfff8ebef, 0xff5b6fab, 0x005f0a0d,
+ 0xffc4b414, 0xff68582c, 0x006b38e5,
+ 0xffabb861, 0xff704bec, 0x0074de52,
+ 0xffa19f4c, 0xff729059, 0x007c7e90,
+ 0x00000003//output gain
+};
+
+static u32 coef_32to176[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x0018a102,//header
+ 0x000005d6,//input gain
+ 0x00c6543e, 0xff342935, 0x0052f116,
+ 0x000a1d78, 0xff3330c0, 0x005f88a3,
+ 0xffbee7c0, 0xff2b5ba5, 0x0073eb26,
+ 0x00000003,//output gain
+ 0x00000204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_32to192[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x0000a102,//header
+ 0x000005d6,//input gain
+ 0x00c6543e, 0xff342935, 0x0052f116,
+ 0x000a1d78, 0xff3330c0, 0x005f88a3,
+ 0xffbee7c0, 0xff2b5ba5, 0x0073eb26,
+ 0x00000003//output gain
+};
+
+static u32 coef_44to8[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00120104,//IIR Filter
+ 0x00000af2,//input gain
+ 0x0057eebe, 0xff1e9863, 0x00652604,
+ 0xff7206ea, 0xff22ad7e, 0x006d47e1,
+ 0xff42a4d7, 0xff26e722, 0x0075fd83,
+ 0xff352f66, 0xff29312b, 0x007b986b,
+ 0xff310a07, 0xff296f51, 0x007eca7c,
+ 0x00000001,//output gain
+ 0x001d9204,//Farrow Filter + decimation
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005105,//IIR Filter + Decimator
+ 0x0000d649,//input gain
+ 0x00e87afb, 0xff5f69d0, 0x003df3cf,
+ 0x007ce488, 0xff99a5c8, 0x0056a6a0,
+ 0x00344928, 0xffcba3e5, 0x006be470,
+ 0x00137aa7, 0xffe60276, 0x00773410,
+ 0x0005fa2a, 0xfff1ac11, 0x007c795b,
+ 0x00012d36, 0xfff5eca2, 0x007f10ef,
+ 0x00000001//output gain
+};
+
+static u32 coef_44to11[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c5102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_44to16[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00126104,//IIR Filter + interpolation
+ 0x00000af2,//input gain
+ 0x0057eebe, 0xff1e9863, 0x00652604,
+ 0xff7206ea, 0xff22ad7e, 0x006d47e1,
+ 0xff42a4d7, 0xff26e722, 0x0075fd83,
+ 0xff352f66, 0xff29312b, 0x007b986b,
+ 0xff310a07, 0xff296f51, 0x007eca7c,
+ 0x00000002,//output gain
+ 0x001d9204,//Farrow Filter + decimation
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005105,//IIR Filter + Decimator
+ 0x0000d649,//input gain
+ 0x00e87afb, 0xff5f69d0, 0x003df3cf,
+ 0x007ce488, 0xff99a5c8, 0x0056a6a0,
+ 0x00344928, 0xffcba3e5, 0x006be470,
+ 0x00137aa7, 0xffe60276, 0x00773410,
+ 0x0005fa2a, 0xfff1ac11, 0x007c795b,
+ 0x00012d36, 0xfff5eca2, 0x007f10ef,
+ 0x00000001//output gain
+};
+
+static u32 coef_44to22[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_44to24[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x00001685,//input gain
+ 0x00f53ae9, 0xff52f196, 0x003e3e08,
+ 0x00b9f857, 0xff5d8985, 0x0050070a,
+ 0x008c3e86, 0xff6053f0, 0x006d98ef,
+ 0x00000002,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_44to32[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0000015f,//input gain
+ 0x00a7909c, 0xff241c71, 0x005f5e00,
+ 0xffca77f4, 0xff20dd50, 0x006855eb,
+ 0xff86c552, 0xff18137a, 0x00773648,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000005f3,//input gain
+ 0x00d816d6, 0xff385383, 0x004fe566,
+ 0x003c548d, 0xff38c23d, 0x005d0b1c,
+ 0xfff02f7d, 0xff31e983, 0x0072d65d,
+ 0x00000002,//output gain
+ 0x00239204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_44to48[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00235204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d029,//input gain
+ 0x00f2a98b, 0xff92aa71, 0x001fcd16,
+ 0x00ae9004, 0xffb85140, 0x0041813a,
+ 0x007f8ed1, 0xffd585fc, 0x006a69e6,
+ 0x00000001//output gain
+};
+
+static u32 coef_44to88[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00006102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002//output gain
+};
+
+static u32 coef_44to96[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00005204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_44to176[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00006102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002//output gain
+};
+
+static u32 coef_44to192[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00246102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x00005204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_48to8[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c9102,//IIR Filter + Decimator
+ 0x00000e00,//input gain
+ 0x00e2e000, 0xff6e1a00, 0x002aaa00,
+ 0x00610a00, 0xff5dda00, 0x003ccc00,
+ 0x00163a00, 0xff3c0400, 0x00633200,
+ 0x00000001,//output gain
+ 0x00005105,//IIR Filter + Decimator
+ 0x0000d649,//input gain
+ 0x00e87afb, 0xff5f69d0, 0x003df3cf,
+ 0x007ce488, 0xff99a5c8, 0x0056a6a0,
+ 0x00344928, 0xffcba3e5, 0x006be470,
+ 0x00137aa7, 0xffe60276, 0x00773410,
+ 0x0005fa2a, 0xfff1ac11, 0x007c795b,
+ 0x00012d36, 0xfff5eca2, 0x007f10ef,
+ 0x00000001//output gain
+};
+
+static u32 coef_48to11[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x000000af,//input gain
+ 0x00c65663, 0xff23d2ce, 0x005f97d6,
+ 0x00086ad6, 0xff20ec4f, 0x00683201,
+ 0xffbbbef6, 0xff184447, 0x00770963,
+ 0x00000002,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00235102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_48to16[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00009105,//IIR Filter + Decimator
+ 0x00000784,//input gain
+ 0x00cc516e, 0xff2c9639, 0x005ad5b3,
+ 0x0013ad0d, 0xff3d4799, 0x0063ce75,
+ 0xffb6f398, 0xff5138d1, 0x006e9e1f,
+ 0xff9186e5, 0xff5f96a4, 0x0076a86e,
+ 0xff82089c, 0xff676b81, 0x007b9f8a,
+ 0xff7c48a5, 0xff6a31e7, 0x007ebb7b,
+ 0x00000001//output gain
+};
+
+static u32 coef_48to22[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000f6103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000002,//output gain
+ 0x001a5204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_48to24[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_48to32[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00156105,//header
+ 0x00000292,//input gain
+ 0x00e4320a, 0xff41d2d9, 0x004911ac,
+ 0x005dd9e3, 0xff4c7d80, 0x0052103e,
+ 0xfff8ebef, 0xff5b6fab, 0x005f0a0d,
+ 0xffc4b414, 0xff68582c, 0x006b38e5,
+ 0xffabb861, 0xff704bec, 0x0074de52,
+ 0xffa19f4c, 0xff729059, 0x007c7e90,
+ 0x00000002,//output gain
+ 0x00009105,//header
+ 0x00000292,//input gain
+ 0x00e4320a, 0xff41d2d9, 0x004911ac,
+ 0x005dd9e3, 0xff4c7d80, 0x0052103e,
+ 0xfff8ebef, 0xff5b6fab, 0x005f0a0d,
+ 0xffc4b414, 0xff68582c, 0x006b38e5,
+ 0xffabb861, 0xff704bec, 0x0074de52,
+ 0xffa19f4c, 0xff729059, 0x007c7e90,
+ 0x00000001//output gain
+};
+
+static u32 coef_48to44[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d029,//input gain
+ 0x00f2a98b, 0xff92aa71, 0x001fcd16,
+ 0x00ae9004, 0xffb85140, 0x0041813a,
+ 0x007f8ed1, 0xffd585fc, 0x006a69e6,
+ 0x00000002,//output gain
+ 0x001b6103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000002,//output gain
+ 0x00265204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_48to88[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00230204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x00001685,//input gain
+ 0x00f53ae9, 0xff52f196, 0x003e3e08,
+ 0x00b9f857, 0xff5d8985, 0x0050070a,
+ 0x008c3e86, 0xff6053f0, 0x006d98ef,
+ 0x00000001//output gain
+};
+
+static u32 coef_48to96[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00006102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002//output gain
+};
+
+static u32 coef_48to176[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00246102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x002f0204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x00000138,//input gain
+ 0x00d5d232, 0xff2a3bf8, 0x005a785c,
+ 0x0034001b, 0xff283109, 0x006462a6,
+ 0xffe6746a, 0xff1fb09c, 0x00758a91,
+ 0x00000001//output gain
+};
+
+static u32 coef_48to192[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000002,//output gain
+ 0x00006102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002//output gain
+};
+
+static u32 coef_88to8[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c0102,//header
+ 0x00000057,//input gain
+ 0x00a8e717, 0xff1c748d, 0x0065b976,
+ 0xffcbccab, 0xff190aff, 0x006cc1cf,
+ 0xff871ce1, 0xff10d878, 0x0078cfc5,
+ 0x00000001,//output gain
+ 0x00179204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00235102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_88to11[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c5102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000001,//output gain
+ 0x00185102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_88to16[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c0102,//header
+ 0x000005f3,//input gain
+ 0x00d816d6, 0xff385383, 0x004fe566,
+ 0x003c548d, 0xff38c23d, 0x005d0b1c,
+ 0xfff02f7d, 0xff31e983, 0x0072d65d,
+ 0x00000001,//output gain
+ 0x00179204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_88to22[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c5102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_88to24[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c0102,//header
+ 0x00001685,//input gain
+ 0x00f53ae9, 0xff52f196, 0x003e3e08,
+ 0x00b9f857, 0xff5d8985, 0x0050070a,
+ 0x008c3e86, 0xff6053f0, 0x006d98ef,
+ 0x00000001,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_88to32[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x000005f3,//input gain
+ 0x00d816d6, 0xff385383, 0x004fe566,
+ 0x003c548d, 0xff38c23d, 0x005d0b1c,
+ 0xfff02f7d, 0xff31e983, 0x0072d65d,
+ 0x00000002,//output gain
+ 0x00179204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_88to44[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_88to48[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x00001685,//input gain
+ 0x00f53ae9, 0xff52f196, 0x003e3e08,
+ 0x00b9f857, 0xff5d8985, 0x0050070a,
+ 0x008c3e86, 0xff6053f0, 0x006d98ef,
+ 0x00000002,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_88to96[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00005204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_88to176[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00006102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002//output gain
+};
+
+static u32 coef_88to192[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000002,//output gain
+ 0x00186102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x00005204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_96to8[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c9102,//header
+ 0x0000007d,//input gain
+ 0x007d1f20, 0xff1a540e, 0x00678bf9,
+ 0xff916625, 0xff16b0ff, 0x006e433a,
+ 0xff5af660, 0xff0eb91f, 0x00797356,
+ 0x00000001,//output gain
+ 0x00185102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_96to11[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c0102,//header
+ 0x000000af,//input gain
+ 0x00c65663, 0xff23d2ce, 0x005f97d6,
+ 0x00086ad6, 0xff20ec4f, 0x00683201,
+ 0xffbbbef6, 0xff184447, 0x00770963,
+ 0x00000001,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00235102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_96to16[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c9102,//header
+ 0x000005d6,//input gain
+ 0x00c6543e, 0xff342935, 0x0052f116,
+ 0x000a1d78, 0xff3330c0, 0x005f88a3,
+ 0xffbee7c0, 0xff2b5ba5, 0x0073eb26,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_96to22[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x000000af,//input gain
+ 0x00c65663, 0xff23d2ce, 0x005f97d6,
+ 0x00086ad6, 0xff20ec4f, 0x00683201,
+ 0xffbbbef6, 0xff184447, 0x00770963,
+ 0x00000002,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00235102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_96to24[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c5102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_96to32[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00009105,//header
+ 0x00000292,//input gain
+ 0x00e4320a, 0xff41d2d9, 0x004911ac,
+ 0x005dd9e3, 0xff4c7d80, 0x0052103e,
+ 0xfff8ebef, 0xff5b6fab, 0x005f0a0d,
+ 0xffc4b414, 0xff68582c, 0x006b38e5,
+ 0xffabb861, 0xff704bec, 0x0074de52,
+ 0xffa19f4c, 0xff729059, 0x007c7e90,
+ 0x00000001//output gain
+};
+
+static u32 coef_96to44[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000f6103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000002,//output gain
+ 0x001a5204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_96to48[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_96to88[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000f6103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000002,//output gain
+ 0x001a0204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001//output gain
+};
+
+static u32 coef_96to176[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000f6103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000002,//output gain
+ 0x001b6102,//header
+ 0x000000af,//input gain
+ 0x00c65663, 0xff23d2ce, 0x005f97d6,
+ 0x00086ad6, 0xff20ec4f, 0x00683201,
+ 0xffbbbef6, 0xff184447, 0x00770963,
+ 0x00000002,//output gain
+ 0x00260204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000001//output gain
+};
+
+static u32 coef_96to192[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00006103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000002//output gain
+};
+
+static u32 coef_176to16[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c0102,//header
+ 0x00000057,//input gain
+ 0x00a8e717, 0xff1c748d, 0x0065b976,
+ 0xffcbccab, 0xff190aff, 0x006cc1cf,
+ 0xff871ce1, 0xff10d878, 0x0078cfc5,
+ 0x00000001,//output gain
+ 0x00179204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00235102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_176to22[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c5102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000001,//output gain
+ 0x00185102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_176to24[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c0102,//header
+ 0x00000138,//input gain
+ 0x00d5d232, 0xff2a3bf8, 0x005a785c,
+ 0x0034001b, 0xff283109, 0x006462a6,
+ 0xffe6746a, 0xff1fb09c, 0x00758a91,
+ 0x00000001,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00235102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_176to32[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c0102,//header
+ 0x000005f3,//input gain
+ 0x00d816d6, 0xff385383, 0x004fe566,
+ 0x003c548d, 0xff38c23d, 0x005d0b1c,
+ 0xfff02f7d, 0xff31e983, 0x0072d65d,
+ 0x00000001,//output gain
+ 0x00179204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_176to44[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c5102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_176to48[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c0102,//header
+ 0x00001685,//input gain
+ 0x00f53ae9, 0xff52f196, 0x003e3e08,
+ 0x00b9f857, 0xff5d8985, 0x0050070a,
+ 0x008c3e86, 0xff6053f0, 0x006d98ef,
+ 0x00000001,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_176to88[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00005102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001//output gain
+};
+
+static u32 coef_176to96[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000001//output gain
+};
+
+static u32 coef_176to192[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000002,//output gain
+ 0x00005204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000
+};
+
+static u32 coef_192to16[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c9102,//header
+ 0x0000007d,//input gain
+ 0x007d1f20, 0xff1a540e, 0x00678bf9,
+ 0xff916625, 0xff16b0ff, 0x006e433a,
+ 0xff5af660, 0xff0eb91f, 0x00797356,
+ 0x00000001,//output gain
+ 0x00185102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_192to22[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c0102,//header
+ 0x000000af,//input gain
+ 0x00c65663, 0xff23d2ce, 0x005f97d6,
+ 0x00086ad6, 0xff20ec4f, 0x00683201,
+ 0xffbbbef6, 0xff184447, 0x00770963,
+ 0x00000001,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00235102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_192to24[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c5102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000001,//output gain
+ 0x00185102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_192to32[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c9102,//header
+ 0x000005d6,//input gain
+ 0x00c6543e, 0xff342935, 0x0052f116,
+ 0x000a1d78, 0xff3330c0, 0x005f88a3,
+ 0xffbee7c0, 0xff2b5ba5, 0x0073eb26,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_192to44[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x000000af,//input gain
+ 0x00c65663, 0xff23d2ce, 0x005f97d6,
+ 0x00086ad6, 0xff20ec4f, 0x00683201,
+ 0xffbbbef6, 0xff184447, 0x00770963,
+ 0x00000002,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00235102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_192to48[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c5102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001,//output gain
+ 0x00005102,//header
+ 0x0001d727,//input gain
+ 0x00fc2fc7, 0xff9bb27b, 0x001c564c,
+ 0x00e55557, 0xffcadd5b, 0x003d80ba,
+ 0x00d13397, 0xfff232f8, 0x00683337,
+ 0x00000001//output gain
+};
+
+static u32 coef_192to88[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x000000af,//input gain
+ 0x00c65663, 0xff23d2ce, 0x005f97d6,
+ 0x00086ad6, 0xff20ec4f, 0x00683201,
+ 0xffbbbef6, 0xff184447, 0x00770963,
+ 0x00000002,//output gain
+ 0x00175204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x000013d9,//input gain
+ 0x00ebd477, 0xff4ce383, 0x0042049d,
+ 0x0089c278, 0xff54414d, 0x00531ded,
+ 0x004a5e07, 0xff53cf41, 0x006efbdc,
+ 0x00000001//output gain
+};
+
+static u32 coef_192to96[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x00005103,//header
+ 0x000001e0,//input gain
+ 0x00de44c0, 0xff380b7f, 0x004ffc73,
+ 0x00494b44, 0xff3d493a, 0x005908bf,
+ 0xffe9a3c8, 0xff425647, 0x006745f7,
+ 0xffc42d61, 0xff40a6c7, 0x00776709,
+ 0x00000001//output gain
+};
+
+static u32 coef_192to176[TEGRA210_SFC_COEF_RAM_DEPTH] = {
+ 0x000c6102,//header
+ 0x000000af,//input gain
+ 0x00c65663, 0xff23d2ce, 0x005f97d6,
+ 0x00086ad6, 0xff20ec4f, 0x00683201,
+ 0xffbbbef6, 0xff184447, 0x00770963,
+ 0x00000002,//output gain
+ 0x00170204,//farrow
+ 0x000aaaab,
+ 0xffaaaaab,
+ 0xfffaaaab,
+ 0x00555555,
+ 0xff600000,
+ 0xfff55555,
+ 0x00155555,
+ 0x00055555,
+ 0xffeaaaab,
+ 0x00200000,
+ 0x00005102,//header
+ 0x0000010a,//input gain
+ 0x00c93dc4, 0xff26f5f6, 0x005d1041,
+ 0x001002c4, 0xff245b76, 0x00666002,
+ 0xffc30a45, 0xff1baecd, 0x00765921,
+ 0x00000001//output gain
+};
+
+/*
+ * Coefficient table for various sample rate conversions. The sample
+ * rates available are as per tegra210_sfc_rates[].
+ */
+static s32 *coef_addr_table[TEGRA210_SFC_NUM_RATES][TEGRA210_SFC_NUM_RATES] = {
+ /* Convertions from 8 kHz */
+ {
+ BYPASS_CONV,
+ coef_8to11,
+ coef_8to16,
+ coef_8to22,
+ coef_8to24,
+ coef_8to32,
+ coef_8to44,
+ coef_8to48,
+ UNSUPP_CONV,
+ coef_8to88,
+ coef_8to96,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ },
+ /* Convertions from 11.025 kHz */
+ {
+ coef_11to8,
+ BYPASS_CONV,
+ coef_11to16,
+ coef_11to22,
+ coef_11to24,
+ coef_11to32,
+ coef_11to44,
+ coef_11to48,
+ UNSUPP_CONV,
+ coef_11to88,
+ coef_11to96,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ },
+ /* Convertions from 16 kHz */
+ {
+ coef_16to8,
+ coef_16to11,
+ BYPASS_CONV,
+ coef_16to22,
+ coef_16to24,
+ coef_16to32,
+ coef_16to44,
+ coef_16to48,
+ UNSUPP_CONV,
+ coef_16to88,
+ coef_16to96,
+ coef_16to176,
+ coef_16to192,
+ },
+ /* Convertions from 22.05 kHz */
+ {
+ coef_22to8,
+ coef_22to11,
+ coef_22to16,
+ BYPASS_CONV,
+ coef_22to24,
+ coef_22to32,
+ coef_22to44,
+ coef_22to48,
+ UNSUPP_CONV,
+ coef_22to88,
+ coef_22to96,
+ coef_22to176,
+ coef_22to192,
+ },
+ /* Convertions from 24 kHz */
+ {
+ coef_24to8,
+ coef_24to11,
+ coef_24to16,
+ coef_24to22,
+ BYPASS_CONV,
+ coef_24to32,
+ coef_24to44,
+ coef_24to48,
+ UNSUPP_CONV,
+ coef_24to88,
+ coef_24to96,
+ coef_24to176,
+ coef_24to192,
+ },
+ /* Convertions from 32 kHz */
+ {
+ coef_32to8,
+ coef_32to11,
+ coef_32to16,
+ coef_32to22,
+ coef_32to24,
+ BYPASS_CONV,
+ coef_32to44,
+ coef_32to48,
+ UNSUPP_CONV,
+ coef_32to88,
+ coef_32to96,
+ coef_32to176,
+ coef_32to192,
+ },
+ /* Convertions from 44.1 kHz */
+ {
+ coef_44to8,
+ coef_44to11,
+ coef_44to16,
+ coef_44to22,
+ coef_44to24,
+ coef_44to32,
+ BYPASS_CONV,
+ coef_44to48,
+ UNSUPP_CONV,
+ coef_44to88,
+ coef_44to96,
+ coef_44to176,
+ coef_44to192,
+ },
+ /* Convertions from 48 kHz */
+ {
+ coef_48to8,
+ coef_48to11,
+ coef_48to16,
+ coef_48to22,
+ coef_48to24,
+ coef_48to32,
+ coef_48to44,
+ BYPASS_CONV,
+ UNSUPP_CONV,
+ coef_48to88,
+ coef_48to96,
+ coef_48to176,
+ coef_48to192,
+ },
+ /* Convertions from 64 kHz */
+ {
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ },
+ /* Convertions from 88.2 kHz */
+ {
+ coef_88to8,
+ coef_88to11,
+ coef_88to16,
+ coef_88to22,
+ coef_88to24,
+ coef_88to32,
+ coef_88to44,
+ coef_88to48,
+ UNSUPP_CONV,
+ BYPASS_CONV,
+ coef_88to96,
+ coef_88to176,
+ coef_88to192,
+ },
+ /* Convertions from 96 kHz */
+ { coef_96to8,
+ coef_96to11,
+ coef_96to16,
+ coef_96to22,
+ coef_96to24,
+ coef_96to32,
+ coef_96to44,
+ coef_96to48,
+ UNSUPP_CONV,
+ coef_96to88,
+ BYPASS_CONV,
+ coef_96to176,
+ coef_96to192,
+ },
+ /* Convertions from 176.4 kHz */
+ {
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ coef_176to16,
+ coef_176to22,
+ coef_176to24,
+ coef_176to32,
+ coef_176to44,
+ coef_176to48,
+ UNSUPP_CONV,
+ coef_176to88,
+ coef_176to96,
+ BYPASS_CONV,
+ coef_176to192,
+ },
+ /* Convertions from 192 kHz */
+ {
+ UNSUPP_CONV,
+ UNSUPP_CONV,
+ coef_192to16,
+ coef_192to22,
+ coef_192to24,
+ coef_192to32,
+ coef_192to44,
+ coef_192to48,
+ UNSUPP_CONV,
+ coef_192to88,
+ coef_192to96,
+ coef_192to176,
+ BYPASS_CONV,
+ },
+};
+
+static int __maybe_unused tegra210_sfc_runtime_suspend(struct device *dev)
+{
+ struct tegra210_sfc *sfc = dev_get_drvdata(dev);
+
+ regcache_cache_only(sfc->regmap, true);
+ regcache_mark_dirty(sfc->regmap);
+
+ return 0;
+}
+
+static int __maybe_unused tegra210_sfc_runtime_resume(struct device *dev)
+{
+ struct tegra210_sfc *sfc = dev_get_drvdata(dev);
+
+ regcache_cache_only(sfc->regmap, false);
+ regcache_sync(sfc->regmap);
+
+ return 0;
+}
+
+static inline void tegra210_sfc_write_ram(struct regmap *regmap,
+ s32 *data)
+{
+ int i;
+
+ regmap_write(regmap, TEGRA210_SFC_CFG_RAM_CTRL,
+ TEGRA210_SFC_RAM_CTRL_SEQ_ACCESS_EN |
+ TEGRA210_SFC_RAM_CTRL_ADDR_INIT_EN |
+ TEGRA210_SFC_RAM_CTRL_RW_WRITE);
+
+ for (i = 0; i < TEGRA210_SFC_COEF_RAM_DEPTH; i++)
+ regmap_write(regmap, TEGRA210_SFC_CFG_RAM_DATA, data[i]);
+}
+
+static int tegra210_sfc_write_coeff_ram(struct snd_soc_component *cmpnt)
+{
+ struct tegra210_sfc *sfc = dev_get_drvdata(cmpnt->dev);
+ s32 *coeff_ram;
+
+ /* Bypass */
+ if (sfc->srate_in == sfc->srate_out)
+ return 0;
+
+ coeff_ram = coef_addr_table[sfc->srate_in][sfc->srate_out];
+ if (IS_ERR_OR_NULL(coeff_ram)) {
+ dev_err(cmpnt->dev,
+ "Conversion from %d to %d Hz is not supported\n",
+ sfc->srate_in, sfc->srate_out);
+
+ return PTR_ERR_OR_ZERO(coeff_ram);
+ }
+
+ tegra210_sfc_write_ram(sfc->regmap, coeff_ram);
+
+ regmap_update_bits(sfc->regmap,
+ TEGRA210_SFC_COEF_RAM,
+ TEGRA210_SFC_COEF_RAM_EN,
+ TEGRA210_SFC_COEF_RAM_EN);
+
+ return 0;
+}
+
+static int tegra210_sfc_set_audio_cif(struct tegra210_sfc *sfc,
+ struct snd_pcm_hw_params *params,
+ unsigned int reg)
+{
+ unsigned int channels, audio_bits, path;
+ 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_S32_LE:
+ audio_bits = TEGRA_ACIF_BITS_32;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ cif_conf.audio_ch = channels;
+ cif_conf.client_ch = channels;
+ cif_conf.audio_bits = audio_bits;
+ cif_conf.client_bits = TEGRA_ACIF_BITS_32;
+
+ if (reg == TEGRA210_SFC_RX_CIF_CTRL)
+ path = SFC_RX_PATH;
+ else
+ path = SFC_TX_PATH;
+
+ cif_conf.stereo_conv = sfc->stereo_to_mono[path];
+ cif_conf.mono_conv = sfc->mono_to_stereo[path];
+
+ tegra_set_cif(sfc->regmap, reg, &cif_conf);
+
+ return 0;
+}
+
+static int tegra210_sfc_soft_reset(struct tegra210_sfc *sfc)
+{
+ u32 val;
+
+ /*
+ * Soft Reset: Below performs module soft reset which clears
+ * all FSM logic, flushes flow control of FIFO and resets the
+ * state register. It also brings module back to disabled
+ * state (without flushing the data in the pipe).
+ */
+ regmap_update_bits(sfc->regmap, TEGRA210_SFC_SOFT_RESET,
+ TEGRA210_SFC_SOFT_RESET_EN, 1);
+
+ return regmap_read_poll_timeout(sfc->regmap,
+ TEGRA210_SFC_SOFT_RESET,
+ val,
+ !(val & TEGRA210_SFC_SOFT_RESET_EN),
+ 10, 10000);
+}
+
+static int tegra210_sfc_rate_to_idx(struct device *dev, int rate,
+ int *rate_idx)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tegra210_sfc_rates); i++) {
+ if (rate == tegra210_sfc_rates[i]) {
+ *rate_idx = i;
+
+ return 0;
+ }
+ }
+
+ dev_err(dev, "Sample rate %d Hz is not supported\n", rate);
+
+ return -EOPNOTSUPP;
+}
+
+static int tegra210_sfc_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct tegra210_sfc *sfc = snd_soc_dai_get_drvdata(dai);
+ int err;
+
+ regmap_update_bits(sfc->regmap, TEGRA210_SFC_COEF_RAM,
+ TEGRA210_SFC_COEF_RAM_EN, 0);
+
+ err = tegra210_sfc_soft_reset(sfc);
+ if (err < 0) {
+ dev_err(dai->dev, "Failed to reset SFC in %s, err = %d\n",
+ __func__, err);
+
+ return err;
+ }
+
+ return 0;
+}
+
+static int tegra210_sfc_in_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct tegra210_sfc *sfc = snd_soc_dai_get_drvdata(dai);
+ struct device *dev = dai->dev;
+ int err;
+
+ err = tegra210_sfc_rate_to_idx(dev, params_rate(params),
+ &sfc->srate_in);
+ if (err < 0)
+ return err;
+
+ err = tegra210_sfc_set_audio_cif(sfc, params, TEGRA210_SFC_RX_CIF_CTRL);
+ if (err < 0) {
+ dev_err(dev, "Can't set SFC RX CIF: %d\n", err);
+ return err;
+ }
+
+ regmap_write(sfc->regmap, TEGRA210_SFC_RX_FREQ, sfc->srate_in);
+
+ return err;
+}
+
+static int tegra210_sfc_out_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct tegra210_sfc *sfc = snd_soc_dai_get_drvdata(dai);
+ struct device *dev = dai->dev;
+ int err;
+
+ err = tegra210_sfc_rate_to_idx(dev, params_rate(params),
+ &sfc->srate_out);
+ if (err < 0)
+ return err;
+
+ err = tegra210_sfc_set_audio_cif(sfc, params, TEGRA210_SFC_TX_CIF_CTRL);
+ if (err < 0) {
+ dev_err(dev, "Can't set SFC TX CIF: %d\n", err);
+ return err;
+ }
+
+ regmap_write(sfc->regmap, TEGRA210_SFC_TX_FREQ, sfc->srate_out);
+
+ return 0;
+}
+
+static int tegra210_sfc_init(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm);
+
+ return tegra210_sfc_write_coeff_ram(cmpnt);
+}
+
+static int tegra210_sfc_iget_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_sfc *sfc = snd_soc_component_get_drvdata(cmpnt);
+
+ ucontrol->value.enumerated.item[0] = sfc->stereo_to_mono[SFC_RX_PATH];
+
+ return 0;
+}
+
+static int tegra210_sfc_iput_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_sfc *sfc = snd_soc_component_get_drvdata(cmpnt);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == sfc->stereo_to_mono[SFC_RX_PATH])
+ return 0;
+
+ sfc->stereo_to_mono[SFC_RX_PATH] = value;
+
+ return 1;
+}
+
+static int tegra210_sfc_iget_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_sfc *sfc = snd_soc_component_get_drvdata(cmpnt);
+
+ ucontrol->value.enumerated.item[0] = sfc->mono_to_stereo[SFC_RX_PATH];
+
+ return 0;
+}
+
+static int tegra210_sfc_iput_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_sfc *sfc = snd_soc_component_get_drvdata(cmpnt);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == sfc->mono_to_stereo[SFC_RX_PATH])
+ return 0;
+
+ sfc->mono_to_stereo[SFC_RX_PATH] = value;
+
+ return 1;
+}
+
+static int tegra210_sfc_oget_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_sfc *sfc = snd_soc_component_get_drvdata(cmpnt);
+
+ ucontrol->value.enumerated.item[0] = sfc->stereo_to_mono[SFC_TX_PATH];
+
+ return 0;
+}
+
+static int tegra210_sfc_oput_stereo_to_mono(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_sfc *sfc = snd_soc_component_get_drvdata(cmpnt);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == sfc->stereo_to_mono[SFC_TX_PATH])
+ return 0;
+
+ sfc->stereo_to_mono[SFC_TX_PATH] = value;
+
+ return 1;
+}
+
+static int tegra210_sfc_oget_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_sfc *sfc = snd_soc_component_get_drvdata(cmpnt);
+
+ ucontrol->value.enumerated.item[0] = sfc->mono_to_stereo[SFC_TX_PATH];
+
+ return 0;
+}
+
+static int tegra210_sfc_oput_mono_to_stereo(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct tegra210_sfc *sfc = snd_soc_component_get_drvdata(cmpnt);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ if (value == sfc->mono_to_stereo[SFC_TX_PATH])
+ return 0;
+
+ sfc->mono_to_stereo[SFC_TX_PATH] = value;
+
+ return 1;
+}
+
+static const struct snd_soc_dai_ops tegra210_sfc_in_dai_ops = {
+ .hw_params = tegra210_sfc_in_hw_params,
+ .startup = tegra210_sfc_startup,
+};
+
+static const struct snd_soc_dai_ops tegra210_sfc_out_dai_ops = {
+ .hw_params = tegra210_sfc_out_hw_params,
+};
+
+static struct snd_soc_dai_driver tegra210_sfc_dais[] = {
+ {
+ .name = "SFC-RX-CIF",
+ .playback = {
+ .stream_name = "RX-CIF-Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .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 = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &tegra210_sfc_in_dai_ops,
+ },
+ {
+ .name = "SFC-TX-CIF",
+ .playback = {
+ .stream_name = "TX-CIF-Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .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 = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &tegra210_sfc_out_dai_ops,
+ },
+};
+
+static const struct snd_soc_dapm_widget tegra210_sfc_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT_E("TX", NULL, 0, TEGRA210_SFC_ENABLE,
+ TEGRA210_SFC_EN_SHIFT, 0,
+ tegra210_sfc_init, SND_SOC_DAPM_PRE_PMU),
+};
+
+#define RESAMPLE_ROUTE(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_sfc_routes[] = {
+ { "TX", NULL, "RX" },
+ RESAMPLE_ROUTE("Playback"),
+ RESAMPLE_ROUTE("Capture"),
+};
+
+static const char * const tegra210_sfc_stereo_conv_text[] = {
+ "CH0", "CH1", "AVG",
+};
+
+static const char * const tegra210_sfc_mono_conv_text[] = {
+ "Zero", "Copy",
+};
+
+static const struct soc_enum tegra210_sfc_stereo_conv_enum =
+ SOC_ENUM_SINGLE(SND_SOC_NOPM, 0,
+ ARRAY_SIZE(tegra210_sfc_stereo_conv_text),
+ tegra210_sfc_stereo_conv_text);
+
+static const struct soc_enum tegra210_sfc_mono_conv_enum =
+ SOC_ENUM_SINGLE(SND_SOC_NOPM, 0,
+ ARRAY_SIZE(tegra210_sfc_mono_conv_text),
+ tegra210_sfc_mono_conv_text);
+
+static const struct snd_kcontrol_new tegra210_sfc_controls[] = {
+ SOC_ENUM_EXT("Input Stereo To Mono", tegra210_sfc_stereo_conv_enum,
+ tegra210_sfc_iget_stereo_to_mono,
+ tegra210_sfc_iput_stereo_to_mono),
+ SOC_ENUM_EXT("Input Mono To Stereo", tegra210_sfc_mono_conv_enum,
+ tegra210_sfc_iget_mono_to_stereo,
+ tegra210_sfc_iput_mono_to_stereo),
+ SOC_ENUM_EXT("Output Stereo To Mono", tegra210_sfc_stereo_conv_enum,
+ tegra210_sfc_oget_stereo_to_mono,
+ tegra210_sfc_oput_stereo_to_mono),
+ SOC_ENUM_EXT("Output Mono To Stereo", tegra210_sfc_mono_conv_enum,
+ tegra210_sfc_oget_mono_to_stereo,
+ tegra210_sfc_oput_mono_to_stereo),
+};
+
+static const struct snd_soc_component_driver tegra210_sfc_cmpnt = {
+ .dapm_widgets = tegra210_sfc_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra210_sfc_widgets),
+ .dapm_routes = tegra210_sfc_routes,
+ .num_dapm_routes = ARRAY_SIZE(tegra210_sfc_routes),
+ .controls = tegra210_sfc_controls,
+ .num_controls = ARRAY_SIZE(tegra210_sfc_controls),
+};
+
+static bool tegra210_sfc_wr_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_SFC_RX_INT_MASK ... TEGRA210_SFC_RX_FREQ:
+ case TEGRA210_SFC_TX_INT_MASK ... TEGRA210_SFC_TX_FREQ:
+ case TEGRA210_SFC_ENABLE ... TEGRA210_SFC_CG:
+ case TEGRA210_SFC_COEF_RAM ... TEGRA210_SFC_CFG_RAM_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_sfc_rd_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_SFC_RX_STATUS ... TEGRA210_SFC_RX_FREQ:
+ case TEGRA210_SFC_TX_STATUS ... TEGRA210_SFC_TX_FREQ:
+ case TEGRA210_SFC_ENABLE ... TEGRA210_SFC_INT_STATUS:
+ case TEGRA210_SFC_COEF_RAM ... TEGRA210_SFC_CFG_RAM_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_sfc_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_SFC_RX_STATUS:
+ case TEGRA210_SFC_RX_INT_STATUS:
+ case TEGRA210_SFC_RX_INT_SET:
+
+ case TEGRA210_SFC_TX_STATUS:
+ case TEGRA210_SFC_TX_INT_STATUS:
+ case TEGRA210_SFC_TX_INT_SET:
+
+ case TEGRA210_SFC_SOFT_RESET:
+ case TEGRA210_SFC_STATUS:
+ case TEGRA210_SFC_INT_STATUS:
+ case TEGRA210_SFC_CFG_RAM_CTRL:
+ case TEGRA210_SFC_CFG_RAM_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra210_sfc_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA210_SFC_CFG_RAM_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tegra210_sfc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA210_SFC_CFG_RAM_DATA,
+ .writeable_reg = tegra210_sfc_wr_reg,
+ .readable_reg = tegra210_sfc_rd_reg,
+ .volatile_reg = tegra210_sfc_volatile_reg,
+ .precious_reg = tegra210_sfc_precious_reg,
+ .reg_defaults = tegra210_sfc_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tegra210_sfc_reg_defaults),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct of_device_id tegra210_sfc_of_match[] = {
+ { .compatible = "nvidia,tegra210-sfc" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra210_sfc_of_match);
+
+static int tegra210_sfc_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tegra210_sfc *sfc;
+ void __iomem *regs;
+ int err;
+
+ sfc = devm_kzalloc(dev, sizeof(*sfc), GFP_KERNEL);
+ if (!sfc)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, sfc);
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ sfc->regmap = devm_regmap_init_mmio(dev, regs,
+ &tegra210_sfc_regmap_config);
+ if (IS_ERR(sfc->regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ return PTR_ERR(sfc->regmap);
+ }
+
+ regcache_cache_only(sfc->regmap, true);
+
+ err = devm_snd_soc_register_component(dev, &tegra210_sfc_cmpnt,
+ tegra210_sfc_dais,
+ ARRAY_SIZE(tegra210_sfc_dais));
+ if (err) {
+ dev_err(dev, "can't register SFC component, err: %d\n", err);
+ return err;
+ }
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+}
+
+static int tegra210_sfc_platform_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra210_sfc_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra210_sfc_runtime_suspend,
+ tegra210_sfc_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver tegra210_sfc_driver = {
+ .driver = {
+ .name = "tegra210-sfc",
+ .of_match_table = tegra210_sfc_of_match,
+ .pm = &tegra210_sfc_pm_ops,
+ },
+ .probe = tegra210_sfc_platform_probe,
+ .remove = tegra210_sfc_platform_remove,
+};
+module_platform_driver(tegra210_sfc_driver)
+
+MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>");
+MODULE_DESCRIPTION("Tegra210 SFC ASoC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/tegra/tegra210_sfc.h b/sound/soc/tegra/tegra210_sfc.h
new file mode 100644
index 000000000..a4c993d79
--- /dev/null
+++ b/sound/soc/tegra/tegra210_sfc.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra210_sfc.h - Definitions for Tegra210 SFC driver
+ *
+ * Copyright (c) 2021-2023 NVIDIA CORPORATION. All rights reserved.
+ *
+ */
+
+#ifndef __TEGRA210_SFC_H__
+#define __TEGRA210_SFC_H__
+
+/*
+ * SFC_RX registers are with respect to XBAR.
+ * The data comes from XBAR to SFC.
+ */
+#define TEGRA210_SFC_RX_STATUS 0x0c
+#define TEGRA210_SFC_RX_INT_STATUS 0x10
+#define TEGRA210_SFC_RX_INT_MASK 0x14
+#define TEGRA210_SFC_RX_INT_SET 0x18
+#define TEGRA210_SFC_RX_INT_CLEAR 0x1c
+#define TEGRA210_SFC_RX_CIF_CTRL 0x20
+#define TEGRA210_SFC_RX_FREQ 0x24
+
+/*
+ * SFC_TX registers are with respect to XBAR.
+ * The data goes out of SFC.
+ */
+#define TEGRA210_SFC_TX_STATUS 0x4c
+#define TEGRA210_SFC_TX_INT_STATUS 0x50
+#define TEGRA210_SFC_TX_INT_MASK 0x54
+#define TEGRA210_SFC_TX_INT_SET 0x58
+#define TEGRA210_SFC_TX_INT_CLEAR 0x5c
+#define TEGRA210_SFC_TX_CIF_CTRL 0x60
+#define TEGRA210_SFC_TX_FREQ 0x64
+
+/* Register offsets from TEGRA210_SFC*_BASE */
+#define TEGRA210_SFC_ENABLE 0x80
+#define TEGRA210_SFC_SOFT_RESET 0x84
+#define TEGRA210_SFC_CG 0x88
+#define TEGRA210_SFC_STATUS 0x8c
+#define TEGRA210_SFC_INT_STATUS 0x90
+#define TEGRA210_SFC_COEF_RAM 0xbc
+#define TEGRA210_SFC_CFG_RAM_CTRL 0xc0
+#define TEGRA210_SFC_CFG_RAM_DATA 0xc4
+
+/* Fields in TEGRA210_SFC_ENABLE */
+#define TEGRA210_SFC_EN_SHIFT 0
+#define TEGRA210_SFC_EN (1 << TEGRA210_SFC_EN_SHIFT)
+
+#define TEGRA210_SFC_NUM_RATES 13
+
+/* Fields in TEGRA210_SFC_COEF_RAM */
+#define TEGRA210_SFC_COEF_RAM_EN BIT(0)
+
+#define TEGRA210_SFC_SOFT_RESET_EN BIT(0)
+
+/* Coefficients */
+#define TEGRA210_SFC_COEF_RAM_DEPTH 64
+#define TEGRA210_SFC_RAM_CTRL_RW_WRITE (1 << 14)
+#define TEGRA210_SFC_RAM_CTRL_ADDR_INIT_EN (1 << 13)
+#define TEGRA210_SFC_RAM_CTRL_SEQ_ACCESS_EN (1 << 12)
+
+
+enum tegra210_sfc_path {
+ SFC_RX_PATH,
+ SFC_TX_PATH,
+ SFC_PATHS,
+};
+
+struct tegra210_sfc {
+ unsigned int mono_to_stereo[SFC_PATHS];
+ unsigned int stereo_to_mono[SFC_PATHS];
+ unsigned int srate_out;
+ unsigned int srate_in;
+ struct regmap *regmap;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra30_ahub.c b/sound/soc/tegra/tegra30_ahub.c
new file mode 100644
index 000000000..ef011a488
--- /dev/null
+++ b/sound/soc/tegra/tegra30_ahub.c
@@ -0,0 +1,690 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tegra30_ahub.c - Tegra30 AHUB driver
+ *
+ * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_platform.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/soc.h>
+#include "tegra30_ahub.h"
+
+#define DRV_NAME "tegra30-ahub"
+
+static struct tegra30_ahub *ahub;
+
+static inline void tegra30_apbif_write(u32 reg, u32 val)
+{
+ regmap_write(ahub->regmap_apbif, reg, val);
+}
+
+static inline u32 tegra30_apbif_read(u32 reg)
+{
+ u32 val;
+
+ regmap_read(ahub->regmap_apbif, reg, &val);
+ return val;
+}
+
+static inline void tegra30_audio_write(u32 reg, u32 val)
+{
+ regmap_write(ahub->regmap_ahub, reg, val);
+}
+
+static __maybe_unused int tegra30_ahub_runtime_suspend(struct device *dev)
+{
+ regcache_cache_only(ahub->regmap_apbif, true);
+ regcache_cache_only(ahub->regmap_ahub, true);
+
+ clk_bulk_disable_unprepare(ahub->nclocks, ahub->clocks);
+
+ return 0;
+}
+
+/*
+ * clk_apbif isn't required for an I2S<->I2S configuration where no PCM data
+ * is read from or sent to memory. However, that's not something the rest of
+ * the driver supports right now, so we'll just treat the two clocks as one
+ * for now.
+ *
+ * These functions should not be a plain ref-count. Instead, each active stream
+ * contributes some requirement to the minimum clock rate, so starting or
+ * stopping streams should dynamically adjust the clock as required. However,
+ * this is not yet implemented.
+ */
+static __maybe_unused int tegra30_ahub_runtime_resume(struct device *dev)
+{
+ int ret;
+
+ ret = reset_control_bulk_assert(ahub->nresets, ahub->resets);
+ if (ret)
+ return ret;
+
+ ret = clk_bulk_prepare_enable(ahub->nclocks, ahub->clocks);
+ if (ret)
+ return ret;
+
+ usleep_range(10, 100);
+
+ ret = reset_control_bulk_deassert(ahub->nresets, ahub->resets);
+ if (ret)
+ goto disable_clocks;
+
+ regcache_cache_only(ahub->regmap_apbif, false);
+ regcache_cache_only(ahub->regmap_ahub, false);
+ regcache_mark_dirty(ahub->regmap_apbif);
+ regcache_mark_dirty(ahub->regmap_ahub);
+
+ ret = regcache_sync(ahub->regmap_apbif);
+ if (ret)
+ goto disable_clocks;
+
+ ret = regcache_sync(ahub->regmap_ahub);
+ if (ret)
+ goto disable_clocks;
+
+ return 0;
+
+disable_clocks:
+ clk_bulk_disable_unprepare(ahub->nclocks, ahub->clocks);
+
+ return ret;
+}
+
+int tegra30_ahub_allocate_rx_fifo(enum tegra30_ahub_rxcif *rxcif,
+ char *dmachan, int dmachan_len,
+ dma_addr_t *fiforeg)
+{
+ int channel;
+ u32 reg, val;
+ struct tegra30_ahub_cif_conf cif_conf;
+
+ channel = find_first_zero_bit(ahub->rx_usage,
+ TEGRA30_AHUB_CHANNEL_CTRL_COUNT);
+ if (channel >= TEGRA30_AHUB_CHANNEL_CTRL_COUNT)
+ return -EBUSY;
+
+ __set_bit(channel, ahub->rx_usage);
+
+ *rxcif = TEGRA30_AHUB_RXCIF_APBIF_RX0 + channel;
+ snprintf(dmachan, dmachan_len, "rx%d", channel);
+ *fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_RXFIFO +
+ (channel * TEGRA30_AHUB_CHANNEL_RXFIFO_STRIDE);
+
+ pm_runtime_get_sync(ahub->dev);
+
+ reg = TEGRA30_AHUB_CHANNEL_CTRL +
+ (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
+ val = tegra30_apbif_read(reg);
+ val &= ~(TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK |
+ TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK);
+ val |= (7 << TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT) |
+ TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_EN |
+ TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_16;
+ tegra30_apbif_write(reg, val);
+
+ cif_conf.threshold = 0;
+ cif_conf.audio_channels = 2;
+ cif_conf.client_channels = 2;
+ cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16;
+ cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16;
+ cif_conf.expand = 0;
+ cif_conf.stereo_conv = 0;
+ cif_conf.replicate = 0;
+ cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX;
+ cif_conf.truncate = 0;
+ cif_conf.mono_conv = 0;
+
+ reg = TEGRA30_AHUB_CIF_RX_CTRL +
+ (channel * TEGRA30_AHUB_CIF_RX_CTRL_STRIDE);
+ ahub->soc_data->set_audio_cif(ahub->regmap_apbif, reg, &cif_conf);
+
+ pm_runtime_put(ahub->dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra30_ahub_allocate_rx_fifo);
+
+int tegra30_ahub_enable_rx_fifo(enum tegra30_ahub_rxcif rxcif)
+{
+ int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0;
+ int reg, val;
+
+ pm_runtime_get_sync(ahub->dev);
+
+ reg = TEGRA30_AHUB_CHANNEL_CTRL +
+ (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
+ val = tegra30_apbif_read(reg);
+ val |= TEGRA30_AHUB_CHANNEL_CTRL_RX_EN;
+ tegra30_apbif_write(reg, val);
+
+ pm_runtime_put(ahub->dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra30_ahub_enable_rx_fifo);
+
+int tegra30_ahub_disable_rx_fifo(enum tegra30_ahub_rxcif rxcif)
+{
+ int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0;
+ int reg, val;
+
+ pm_runtime_get_sync(ahub->dev);
+
+ reg = TEGRA30_AHUB_CHANNEL_CTRL +
+ (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
+ val = tegra30_apbif_read(reg);
+ val &= ~TEGRA30_AHUB_CHANNEL_CTRL_RX_EN;
+ tegra30_apbif_write(reg, val);
+
+ pm_runtime_put(ahub->dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra30_ahub_disable_rx_fifo);
+
+int tegra30_ahub_free_rx_fifo(enum tegra30_ahub_rxcif rxcif)
+{
+ int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0;
+
+ __clear_bit(channel, ahub->rx_usage);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra30_ahub_free_rx_fifo);
+
+int tegra30_ahub_allocate_tx_fifo(enum tegra30_ahub_txcif *txcif,
+ char *dmachan, int dmachan_len,
+ dma_addr_t *fiforeg)
+{
+ int channel;
+ u32 reg, val;
+ struct tegra30_ahub_cif_conf cif_conf;
+
+ channel = find_first_zero_bit(ahub->tx_usage,
+ TEGRA30_AHUB_CHANNEL_CTRL_COUNT);
+ if (channel >= TEGRA30_AHUB_CHANNEL_CTRL_COUNT)
+ return -EBUSY;
+
+ __set_bit(channel, ahub->tx_usage);
+
+ *txcif = TEGRA30_AHUB_TXCIF_APBIF_TX0 + channel;
+ snprintf(dmachan, dmachan_len, "tx%d", channel);
+ *fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_TXFIFO +
+ (channel * TEGRA30_AHUB_CHANNEL_TXFIFO_STRIDE);
+
+ pm_runtime_get_sync(ahub->dev);
+
+ reg = TEGRA30_AHUB_CHANNEL_CTRL +
+ (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
+ val = tegra30_apbif_read(reg);
+ val &= ~(TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK |
+ TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK);
+ val |= (7 << TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT) |
+ TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_EN |
+ TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_16;
+ tegra30_apbif_write(reg, val);
+
+ cif_conf.threshold = 0;
+ cif_conf.audio_channels = 2;
+ cif_conf.client_channels = 2;
+ cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16;
+ cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16;
+ cif_conf.expand = 0;
+ cif_conf.stereo_conv = 0;
+ cif_conf.replicate = 0;
+ cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX;
+ cif_conf.truncate = 0;
+ cif_conf.mono_conv = 0;
+
+ reg = TEGRA30_AHUB_CIF_TX_CTRL +
+ (channel * TEGRA30_AHUB_CIF_TX_CTRL_STRIDE);
+ ahub->soc_data->set_audio_cif(ahub->regmap_apbif, reg, &cif_conf);
+
+ pm_runtime_put(ahub->dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra30_ahub_allocate_tx_fifo);
+
+int tegra30_ahub_enable_tx_fifo(enum tegra30_ahub_txcif txcif)
+{
+ int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0;
+ int reg, val;
+
+ pm_runtime_get_sync(ahub->dev);
+
+ reg = TEGRA30_AHUB_CHANNEL_CTRL +
+ (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
+ val = tegra30_apbif_read(reg);
+ val |= TEGRA30_AHUB_CHANNEL_CTRL_TX_EN;
+ tegra30_apbif_write(reg, val);
+
+ pm_runtime_put(ahub->dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra30_ahub_enable_tx_fifo);
+
+int tegra30_ahub_disable_tx_fifo(enum tegra30_ahub_txcif txcif)
+{
+ int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0;
+ int reg, val;
+
+ pm_runtime_get_sync(ahub->dev);
+
+ reg = TEGRA30_AHUB_CHANNEL_CTRL +
+ (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE);
+ val = tegra30_apbif_read(reg);
+ val &= ~TEGRA30_AHUB_CHANNEL_CTRL_TX_EN;
+ tegra30_apbif_write(reg, val);
+
+ pm_runtime_put(ahub->dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra30_ahub_disable_tx_fifo);
+
+int tegra30_ahub_free_tx_fifo(enum tegra30_ahub_txcif txcif)
+{
+ int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0;
+
+ __clear_bit(channel, ahub->tx_usage);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra30_ahub_free_tx_fifo);
+
+int tegra30_ahub_set_rx_cif_source(enum tegra30_ahub_rxcif rxcif,
+ enum tegra30_ahub_txcif txcif)
+{
+ int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0;
+ int reg;
+
+ pm_runtime_get_sync(ahub->dev);
+
+ reg = TEGRA30_AHUB_AUDIO_RX +
+ (channel * TEGRA30_AHUB_AUDIO_RX_STRIDE);
+ tegra30_audio_write(reg, 1 << txcif);
+
+ pm_runtime_put(ahub->dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra30_ahub_set_rx_cif_source);
+
+int tegra30_ahub_unset_rx_cif_source(enum tegra30_ahub_rxcif rxcif)
+{
+ int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0;
+ int reg;
+
+ pm_runtime_get_sync(ahub->dev);
+
+ reg = TEGRA30_AHUB_AUDIO_RX +
+ (channel * TEGRA30_AHUB_AUDIO_RX_STRIDE);
+ tegra30_audio_write(reg, 0);
+
+ pm_runtime_put(ahub->dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra30_ahub_unset_rx_cif_source);
+
+static const struct reset_control_bulk_data tegra30_ahub_resets_data[] = {
+ { "d_audio" },
+ { "apbif" },
+ { "i2s0" },
+ { "i2s1" },
+ { "i2s2" },
+ { "i2s3" },
+ { "i2s4" },
+ { "dam0" },
+ { "dam1" },
+ { "dam2" },
+ { "spdif" },
+ { "amx" }, /* Tegra114+ */
+ { "adx" }, /* Tegra114+ */
+ { "amx1" }, /* Tegra124 */
+ { "adx1" }, /* Tegra124 */
+ { "afc0" }, /* Tegra124 */
+ { "afc1" }, /* Tegra124 */
+ { "afc2" }, /* Tegra124 */
+ { "afc3" }, /* Tegra124 */
+ { "afc4" }, /* Tegra124 */
+ { "afc5" }, /* Tegra124 */
+};
+
+#define LAST_REG(name) \
+ (TEGRA30_AHUB_##name + \
+ (TEGRA30_AHUB_##name##_STRIDE * TEGRA30_AHUB_##name##_COUNT) - 4)
+
+#define REG_IN_ARRAY(reg, name) \
+ ((reg >= TEGRA30_AHUB_##name) && \
+ (reg <= LAST_REG(name) && \
+ (!((reg - TEGRA30_AHUB_##name) % TEGRA30_AHUB_##name##_STRIDE))))
+
+static bool tegra30_ahub_apbif_wr_rd_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA30_AHUB_CONFIG_LINK_CTRL:
+ case TEGRA30_AHUB_MISC_CTRL:
+ case TEGRA30_AHUB_APBDMA_LIVE_STATUS:
+ case TEGRA30_AHUB_I2S_LIVE_STATUS:
+ case TEGRA30_AHUB_SPDIF_LIVE_STATUS:
+ case TEGRA30_AHUB_I2S_INT_MASK:
+ case TEGRA30_AHUB_DAM_INT_MASK:
+ case TEGRA30_AHUB_SPDIF_INT_MASK:
+ case TEGRA30_AHUB_APBIF_INT_MASK:
+ case TEGRA30_AHUB_I2S_INT_STATUS:
+ case TEGRA30_AHUB_DAM_INT_STATUS:
+ case TEGRA30_AHUB_SPDIF_INT_STATUS:
+ case TEGRA30_AHUB_APBIF_INT_STATUS:
+ case TEGRA30_AHUB_I2S_INT_SOURCE:
+ case TEGRA30_AHUB_DAM_INT_SOURCE:
+ case TEGRA30_AHUB_SPDIF_INT_SOURCE:
+ case TEGRA30_AHUB_APBIF_INT_SOURCE:
+ case TEGRA30_AHUB_I2S_INT_SET:
+ case TEGRA30_AHUB_DAM_INT_SET:
+ case TEGRA30_AHUB_SPDIF_INT_SET:
+ case TEGRA30_AHUB_APBIF_INT_SET:
+ return true;
+ default:
+ break;
+ }
+
+ if (REG_IN_ARRAY(reg, CHANNEL_CTRL) ||
+ REG_IN_ARRAY(reg, CHANNEL_CLEAR) ||
+ REG_IN_ARRAY(reg, CHANNEL_STATUS) ||
+ REG_IN_ARRAY(reg, CHANNEL_TXFIFO) ||
+ REG_IN_ARRAY(reg, CHANNEL_RXFIFO) ||
+ REG_IN_ARRAY(reg, CIF_TX_CTRL) ||
+ REG_IN_ARRAY(reg, CIF_RX_CTRL) ||
+ REG_IN_ARRAY(reg, DAM_LIVE_STATUS))
+ return true;
+
+ return false;
+}
+
+static bool tegra30_ahub_apbif_volatile_reg(struct device *dev,
+ unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA30_AHUB_CONFIG_LINK_CTRL:
+ case TEGRA30_AHUB_MISC_CTRL:
+ case TEGRA30_AHUB_APBDMA_LIVE_STATUS:
+ case TEGRA30_AHUB_I2S_LIVE_STATUS:
+ case TEGRA30_AHUB_SPDIF_LIVE_STATUS:
+ case TEGRA30_AHUB_I2S_INT_STATUS:
+ case TEGRA30_AHUB_DAM_INT_STATUS:
+ case TEGRA30_AHUB_SPDIF_INT_STATUS:
+ case TEGRA30_AHUB_APBIF_INT_STATUS:
+ case TEGRA30_AHUB_I2S_INT_SET:
+ case TEGRA30_AHUB_DAM_INT_SET:
+ case TEGRA30_AHUB_SPDIF_INT_SET:
+ case TEGRA30_AHUB_APBIF_INT_SET:
+ return true;
+ default:
+ break;
+ }
+
+ if (REG_IN_ARRAY(reg, CHANNEL_CLEAR) ||
+ REG_IN_ARRAY(reg, CHANNEL_STATUS) ||
+ REG_IN_ARRAY(reg, CHANNEL_TXFIFO) ||
+ REG_IN_ARRAY(reg, CHANNEL_RXFIFO) ||
+ REG_IN_ARRAY(reg, DAM_LIVE_STATUS))
+ return true;
+
+ return false;
+}
+
+static bool tegra30_ahub_apbif_precious_reg(struct device *dev,
+ unsigned int reg)
+{
+ if (REG_IN_ARRAY(reg, CHANNEL_TXFIFO) ||
+ REG_IN_ARRAY(reg, CHANNEL_RXFIFO))
+ return true;
+
+ return false;
+}
+
+static const struct regmap_config tegra30_ahub_apbif_regmap_config = {
+ .name = "apbif",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = TEGRA30_AHUB_APBIF_INT_SET,
+ .writeable_reg = tegra30_ahub_apbif_wr_rd_reg,
+ .readable_reg = tegra30_ahub_apbif_wr_rd_reg,
+ .volatile_reg = tegra30_ahub_apbif_volatile_reg,
+ .precious_reg = tegra30_ahub_apbif_precious_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static bool tegra30_ahub_ahub_wr_rd_reg(struct device *dev, unsigned int reg)
+{
+ if (REG_IN_ARRAY(reg, AUDIO_RX))
+ return true;
+
+ return false;
+}
+
+static const struct regmap_config tegra30_ahub_ahub_regmap_config = {
+ .name = "ahub",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = LAST_REG(AUDIO_RX),
+ .writeable_reg = tegra30_ahub_ahub_wr_rd_reg,
+ .readable_reg = tegra30_ahub_ahub_wr_rd_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static struct tegra30_ahub_soc_data soc_data_tegra30 = {
+ .num_resets = 11,
+ .set_audio_cif = tegra30_ahub_set_cif,
+};
+
+static struct tegra30_ahub_soc_data soc_data_tegra114 = {
+ .num_resets = 13,
+ .set_audio_cif = tegra30_ahub_set_cif,
+};
+
+static struct tegra30_ahub_soc_data soc_data_tegra124 = {
+ .num_resets = 21,
+ .set_audio_cif = tegra124_ahub_set_cif,
+};
+
+static const struct of_device_id tegra30_ahub_of_match[] = {
+ { .compatible = "nvidia,tegra124-ahub", .data = &soc_data_tegra124 },
+ { .compatible = "nvidia,tegra114-ahub", .data = &soc_data_tegra114 },
+ { .compatible = "nvidia,tegra30-ahub", .data = &soc_data_tegra30 },
+ {},
+};
+
+static int tegra30_ahub_probe(struct platform_device *pdev)
+{
+ const struct tegra30_ahub_soc_data *soc_data;
+ struct resource *res0;
+ void __iomem *regs_apbif, *regs_ahub;
+ int ret = 0;
+
+ soc_data = of_device_get_match_data(&pdev->dev);
+ if (!soc_data)
+ return -EINVAL;
+
+ ahub = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_ahub),
+ GFP_KERNEL);
+ if (!ahub)
+ return -ENOMEM;
+ dev_set_drvdata(&pdev->dev, ahub);
+
+ BUILD_BUG_ON(sizeof(ahub->resets) != sizeof(tegra30_ahub_resets_data));
+ memcpy(ahub->resets, tegra30_ahub_resets_data, sizeof(ahub->resets));
+
+ ahub->nresets = soc_data->num_resets;
+ ahub->soc_data = soc_data;
+ ahub->dev = &pdev->dev;
+
+ ahub->clocks[ahub->nclocks++].id = "apbif";
+ ahub->clocks[ahub->nclocks++].id = "d_audio";
+
+ ret = devm_clk_bulk_get(&pdev->dev, ahub->nclocks, ahub->clocks);
+ if (ret)
+ goto err_unset_ahub;
+
+ ret = devm_reset_control_bulk_get_exclusive(&pdev->dev, ahub->nresets,
+ ahub->resets);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't get resets: %d\n", ret);
+ goto err_unset_ahub;
+ }
+
+ regs_apbif = devm_platform_get_and_ioremap_resource(pdev, 0, &res0);
+ if (IS_ERR(regs_apbif)) {
+ ret = PTR_ERR(regs_apbif);
+ goto err_unset_ahub;
+ }
+
+ ahub->apbif_addr = res0->start;
+
+ ahub->regmap_apbif = devm_regmap_init_mmio(&pdev->dev, regs_apbif,
+ &tegra30_ahub_apbif_regmap_config);
+ if (IS_ERR(ahub->regmap_apbif)) {
+ dev_err(&pdev->dev, "apbif regmap init failed\n");
+ ret = PTR_ERR(ahub->regmap_apbif);
+ goto err_unset_ahub;
+ }
+ regcache_cache_only(ahub->regmap_apbif, true);
+
+ regs_ahub = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(regs_ahub)) {
+ ret = PTR_ERR(regs_ahub);
+ goto err_unset_ahub;
+ }
+
+ ahub->regmap_ahub = devm_regmap_init_mmio(&pdev->dev, regs_ahub,
+ &tegra30_ahub_ahub_regmap_config);
+ if (IS_ERR(ahub->regmap_ahub)) {
+ dev_err(&pdev->dev, "ahub regmap init failed\n");
+ ret = PTR_ERR(ahub->regmap_ahub);
+ goto err_unset_ahub;
+ }
+ regcache_cache_only(ahub->regmap_ahub, true);
+
+ pm_runtime_enable(&pdev->dev);
+
+ of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
+
+ return 0;
+
+err_unset_ahub:
+ ahub = NULL;
+
+ return ret;
+}
+
+static int tegra30_ahub_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ ahub = NULL;
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra30_ahub_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra30_ahub_runtime_suspend,
+ tegra30_ahub_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver tegra30_ahub_driver = {
+ .probe = tegra30_ahub_probe,
+ .remove = tegra30_ahub_remove,
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = tegra30_ahub_of_match,
+ .pm = &tegra30_ahub_pm_ops,
+ },
+};
+module_platform_driver(tegra30_ahub_driver);
+
+void tegra30_ahub_set_cif(struct regmap *regmap, unsigned int reg,
+ struct tegra30_ahub_cif_conf *conf)
+{
+ unsigned int value;
+
+ value = (conf->threshold <<
+ TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) |
+ ((conf->audio_channels - 1) <<
+ TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) |
+ ((conf->client_channels - 1) <<
+ TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) |
+ (conf->audio_bits <<
+ TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) |
+ (conf->client_bits <<
+ TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) |
+ (conf->expand <<
+ TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) |
+ (conf->stereo_conv <<
+ TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) |
+ (conf->replicate <<
+ TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) |
+ (conf->direction <<
+ TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) |
+ (conf->truncate <<
+ TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) |
+ (conf->mono_conv <<
+ TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT);
+
+ regmap_write(regmap, reg, value);
+}
+EXPORT_SYMBOL_GPL(tegra30_ahub_set_cif);
+
+void tegra124_ahub_set_cif(struct regmap *regmap, unsigned int reg,
+ struct tegra30_ahub_cif_conf *conf)
+{
+ unsigned int value;
+
+ value = (conf->threshold <<
+ TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) |
+ ((conf->audio_channels - 1) <<
+ TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) |
+ ((conf->client_channels - 1) <<
+ TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) |
+ (conf->audio_bits <<
+ TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) |
+ (conf->client_bits <<
+ TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) |
+ (conf->expand <<
+ TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) |
+ (conf->stereo_conv <<
+ TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) |
+ (conf->replicate <<
+ TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) |
+ (conf->direction <<
+ TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) |
+ (conf->truncate <<
+ TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) |
+ (conf->mono_conv <<
+ TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT);
+
+ regmap_write(regmap, reg, value);
+}
+EXPORT_SYMBOL_GPL(tegra124_ahub_set_cif);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra30 AHUB driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_DEVICE_TABLE(of, tegra30_ahub_of_match);
diff --git a/sound/soc/tegra/tegra30_ahub.h b/sound/soc/tegra/tegra30_ahub.h
new file mode 100644
index 000000000..c9eaf4ec8
--- /dev/null
+++ b/sound/soc/tegra/tegra30_ahub.h
@@ -0,0 +1,525 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra30_ahub.h - Definitions for Tegra30 AHUB driver
+ *
+ * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved.
+ */
+
+#ifndef __TEGRA30_AHUB_H__
+#define __TEGRA30_AHUB_H__
+
+/* Fields in *_CIF_RX/TX_CTRL; used by AHUB FIFOs, and all other audio modules */
+
+#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT 28
+#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US 0xf
+#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK (TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT)
+
+#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT 24
+#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US 0x3f
+#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK (TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT)
+
+/* Channel count minus 1 */
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT 24
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US 7
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT)
+
+/* Channel count minus 1 */
+#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT 20
+#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US 0xf
+#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK (TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT)
+
+/* Channel count minus 1 */
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT 16
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US 7
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT)
+
+/* Channel count minus 1 */
+#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT 16
+#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US 0xf
+#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT)
+
+#define TEGRA30_AUDIOCIF_BITS_4 0
+#define TEGRA30_AUDIOCIF_BITS_8 1
+#define TEGRA30_AUDIOCIF_BITS_12 2
+#define TEGRA30_AUDIOCIF_BITS_16 3
+#define TEGRA30_AUDIOCIF_BITS_20 4
+#define TEGRA30_AUDIOCIF_BITS_24 5
+#define TEGRA30_AUDIOCIF_BITS_28 6
+#define TEGRA30_AUDIOCIF_BITS_32 7
+
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT 12
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_MASK (7 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_4 (TEGRA30_AUDIOCIF_BITS_4 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_8 (TEGRA30_AUDIOCIF_BITS_8 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_12 (TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_16 (TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_20 (TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_24 (TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_28 (TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_32 (TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT)
+
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT 8
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_MASK (7 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_4 (TEGRA30_AUDIOCIF_BITS_4 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_8 (TEGRA30_AUDIOCIF_BITS_8 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_12 (TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_16 (TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_20 (TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_24 (TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_28 (TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_32 (TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT)
+
+#define TEGRA30_AUDIOCIF_EXPAND_ZERO 0
+#define TEGRA30_AUDIOCIF_EXPAND_ONE 1
+#define TEGRA30_AUDIOCIF_EXPAND_LFSR 2
+
+#define TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT 6
+#define TEGRA30_AUDIOCIF_CTRL_EXPAND_MASK (3 << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ZERO (TEGRA30_AUDIOCIF_EXPAND_ZERO << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ONE (TEGRA30_AUDIOCIF_EXPAND_ONE << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_EXPAND_LFSR (TEGRA30_AUDIOCIF_EXPAND_LFSR << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT)
+
+#define TEGRA30_AUDIOCIF_STEREO_CONV_CH0 0
+#define TEGRA30_AUDIOCIF_STEREO_CONV_CH1 1
+#define TEGRA30_AUDIOCIF_STEREO_CONV_AVG 2
+
+#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT 4
+#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_MASK (3 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH0 (TEGRA30_AUDIOCIF_STEREO_CONV_CH0 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH1 (TEGRA30_AUDIOCIF_STEREO_CONV_CH1 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_AVG (TEGRA30_AUDIOCIF_STEREO_CONV_AVG << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT)
+
+#define TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT 3
+
+#define TEGRA30_AUDIOCIF_DIRECTION_TX 0
+#define TEGRA30_AUDIOCIF_DIRECTION_RX 1
+
+#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT 2
+#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_MASK (1 << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_TX (TEGRA30_AUDIOCIF_DIRECTION_TX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_RX (TEGRA30_AUDIOCIF_DIRECTION_RX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT)
+
+#define TEGRA30_AUDIOCIF_TRUNCATE_ROUND 0
+#define TEGRA30_AUDIOCIF_TRUNCATE_CHOP 1
+
+#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT 1
+#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_MASK (1 << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_ROUND (TEGRA30_AUDIOCIF_TRUNCATE_ROUND << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_CHOP (TEGRA30_AUDIOCIF_TRUNCATE_CHOP << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT)
+
+#define TEGRA30_AUDIOCIF_MONO_CONV_ZERO 0
+#define TEGRA30_AUDIOCIF_MONO_CONV_COPY 1
+
+#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT 0
+#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_MASK (1 << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_ZERO (TEGRA30_AUDIOCIF_MONO_CONV_ZERO << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT)
+#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_COPY (TEGRA30_AUDIOCIF_MONO_CONV_COPY << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT)
+
+/* Registers within TEGRA30_AUDIO_CLUSTER_BASE */
+
+/* TEGRA30_AHUB_CHANNEL_CTRL */
+
+#define TEGRA30_AHUB_CHANNEL_CTRL 0x0
+#define TEGRA30_AHUB_CHANNEL_CTRL_STRIDE 0x20
+#define TEGRA30_AHUB_CHANNEL_CTRL_COUNT 4
+#define TEGRA30_AHUB_CHANNEL_CTRL_TX_EN (1 << 31)
+#define TEGRA30_AHUB_CHANNEL_CTRL_RX_EN (1 << 30)
+#define TEGRA30_AHUB_CHANNEL_CTRL_LOOPBACK (1 << 29)
+
+#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT 16
+#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US 0xff
+#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK (TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT)
+
+#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT 8
+#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US 0xff
+#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK (TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT)
+
+#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_EN (1 << 6)
+
+#define TEGRA30_PACK_8_4 2
+#define TEGRA30_PACK_16 3
+
+#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT 4
+#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US 3
+#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK (TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT)
+#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_8_4 (TEGRA30_PACK_8_4 << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT)
+#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_16 (TEGRA30_PACK_16 << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT)
+
+#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_EN (1 << 2)
+
+#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT 0
+#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US 3
+#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK (TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT)
+#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_8_4 (TEGRA30_PACK_8_4 << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT)
+#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_16 (TEGRA30_PACK_16 << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT)
+
+/* TEGRA30_AHUB_CHANNEL_CLEAR */
+
+#define TEGRA30_AHUB_CHANNEL_CLEAR 0x4
+#define TEGRA30_AHUB_CHANNEL_CLEAR_STRIDE 0x20
+#define TEGRA30_AHUB_CHANNEL_CLEAR_COUNT 4
+#define TEGRA30_AHUB_CHANNEL_CLEAR_TX_SOFT_RESET (1 << 31)
+#define TEGRA30_AHUB_CHANNEL_CLEAR_RX_SOFT_RESET (1 << 30)
+
+/* TEGRA30_AHUB_CHANNEL_STATUS */
+
+#define TEGRA30_AHUB_CHANNEL_STATUS 0x8
+#define TEGRA30_AHUB_CHANNEL_STATUS_STRIDE 0x20
+#define TEGRA30_AHUB_CHANNEL_STATUS_COUNT 4
+#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT 24
+#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US 0xff
+#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK (TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US << TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT)
+#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT 16
+#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US 0xff
+#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK (TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US << TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT)
+#define TEGRA30_AHUB_CHANNEL_STATUS_TX_TRIG (1 << 1)
+#define TEGRA30_AHUB_CHANNEL_STATUS_RX_TRIG (1 << 0)
+
+/* TEGRA30_AHUB_CHANNEL_TXFIFO */
+
+#define TEGRA30_AHUB_CHANNEL_TXFIFO 0xc
+#define TEGRA30_AHUB_CHANNEL_TXFIFO_STRIDE 0x20
+#define TEGRA30_AHUB_CHANNEL_TXFIFO_COUNT 4
+
+/* TEGRA30_AHUB_CHANNEL_RXFIFO */
+
+#define TEGRA30_AHUB_CHANNEL_RXFIFO 0x10
+#define TEGRA30_AHUB_CHANNEL_RXFIFO_STRIDE 0x20
+#define TEGRA30_AHUB_CHANNEL_RXFIFO_COUNT 4
+
+/* TEGRA30_AHUB_CIF_TX_CTRL */
+
+#define TEGRA30_AHUB_CIF_TX_CTRL 0x14
+#define TEGRA30_AHUB_CIF_TX_CTRL_STRIDE 0x20
+#define TEGRA30_AHUB_CIF_TX_CTRL_COUNT 4
+/* Uses field from TEGRA30_AUDIOCIF_CTRL_* */
+
+/* TEGRA30_AHUB_CIF_RX_CTRL */
+
+#define TEGRA30_AHUB_CIF_RX_CTRL 0x18
+#define TEGRA30_AHUB_CIF_RX_CTRL_STRIDE 0x20
+#define TEGRA30_AHUB_CIF_RX_CTRL_COUNT 4
+/* Uses field from TEGRA30_AUDIOCIF_CTRL_* */
+
+/* TEGRA30_AHUB_CONFIG_LINK_CTRL */
+
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL 0x80
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT 28
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US 0xf
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT)
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT 16
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US 0xfff
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT)
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT 4
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US 0xfff
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT)
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_CG_EN (1 << 2)
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_CLEAR_TIMEOUT_CNTR (1 << 1)
+#define TEGRA30_AHUB_CONFIG_LINK_CTRL_SOFT_RESET (1 << 0)
+
+/* TEGRA30_AHUB_MISC_CTRL */
+
+#define TEGRA30_AHUB_MISC_CTRL 0x84
+#define TEGRA30_AHUB_MISC_CTRL_AUDIO_ACTIVE (1 << 31)
+#define TEGRA30_AHUB_MISC_CTRL_AUDIO_CG_EN (1 << 8)
+#define TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT 0
+#define TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_MASK (0x1f << TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT)
+
+/* TEGRA30_AHUB_APBDMA_LIVE_STATUS */
+
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS 0x88
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_FULL (1 << 31)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_FULL (1 << 30)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_FULL (1 << 29)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_FULL (1 << 28)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_FULL (1 << 27)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_FULL (1 << 26)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_FULL (1 << 25)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_FULL (1 << 24)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_EMPTY (1 << 23)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_EMPTY (1 << 22)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_EMPTY (1 << 21)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_EMPTY (1 << 20)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_EMPTY (1 << 19)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_EMPTY (1 << 18)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_EMPTY (1 << 17)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_EMPTY (1 << 16)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_FULL (1 << 15)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_FULL (1 << 14)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_FULL (1 << 13)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_FULL (1 << 12)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_FULL (1 << 11)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_FULL (1 << 10)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_FULL (1 << 9)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_FULL (1 << 8)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_EMPTY (1 << 7)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_EMPTY (1 << 6)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_EMPTY (1 << 5)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_EMPTY (1 << 4)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_EMPTY (1 << 3)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_EMPTY (1 << 2)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_EMPTY (1 << 1)
+#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_EMPTY (1 << 0)
+
+/* TEGRA30_AHUB_I2S_LIVE_STATUS */
+
+#define TEGRA30_AHUB_I2S_LIVE_STATUS 0x8c
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_FULL (1 << 29)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_FULL (1 << 28)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_FULL (1 << 27)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_FULL (1 << 26)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_FULL (1 << 25)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_FULL (1 << 24)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_FULL (1 << 23)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_FULL (1 << 22)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_FULL (1 << 21)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_FULL (1 << 20)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_ENABLED (1 << 19)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_ENABLED (1 << 18)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_ENABLED (1 << 17)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_ENABLED (1 << 16)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_ENABLED (1 << 15)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_ENABLED (1 << 14)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_ENABLED (1 << 13)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_ENABLED (1 << 12)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_ENABLED (1 << 11)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_ENABLED (1 << 10)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_EMPTY (1 << 9)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_EMPTY (1 << 8)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_EMPTY (1 << 7)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_EMPTY (1 << 6)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_EMPTY (1 << 5)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_EMPTY (1 << 4)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_EMPTY (1 << 3)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_EMPTY (1 << 2)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_EMPTY (1 << 1)
+#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_EMPTY (1 << 0)
+
+/* TEGRA30_AHUB_DAM0_LIVE_STATUS */
+
+#define TEGRA30_AHUB_DAM_LIVE_STATUS 0x90
+#define TEGRA30_AHUB_DAM_LIVE_STATUS_STRIDE 0x8
+#define TEGRA30_AHUB_DAM_LIVE_STATUS_COUNT 3
+#define TEGRA30_AHUB_DAM_LIVE_STATUS_TX_ENABLED (1 << 26)
+#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1_ENABLED (1 << 25)
+#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0_ENABLED (1 << 24)
+#define TEGRA30_AHUB_DAM_LIVE_STATUS_TXFIFO_FULL (1 << 15)
+#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1FIFO_FULL (1 << 9)
+#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0FIFO_FULL (1 << 8)
+#define TEGRA30_AHUB_DAM_LIVE_STATUS_TXFIFO_EMPTY (1 << 7)
+#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1FIFO_EMPTY (1 << 1)
+#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0FIFO_EMPTY (1 << 0)
+
+/* TEGRA30_AHUB_SPDIF_LIVE_STATUS */
+
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS 0xa8
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TX_ENABLED (1 << 11)
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RX_ENABLED (1 << 10)
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TX_ENABLED (1 << 9)
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RX_ENABLED (1 << 8)
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_FULL (1 << 7)
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_FULL (1 << 6)
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_FULL (1 << 5)
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_FULL (1 << 4)
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_EMPTY (1 << 3)
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_EMPTY (1 << 2)
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_EMPTY (1 << 1)
+#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_EMPTY (1 << 0)
+
+/* TEGRA30_AHUB_I2S_INT_MASK */
+
+#define TEGRA30_AHUB_I2S_INT_MASK 0xb0
+
+/* TEGRA30_AHUB_DAM_INT_MASK */
+
+#define TEGRA30_AHUB_DAM_INT_MASK 0xb4
+
+/* TEGRA30_AHUB_SPDIF_INT_MASK */
+
+#define TEGRA30_AHUB_SPDIF_INT_MASK 0xbc
+
+/* TEGRA30_AHUB_APBIF_INT_MASK */
+
+#define TEGRA30_AHUB_APBIF_INT_MASK 0xc0
+
+/* TEGRA30_AHUB_I2S_INT_STATUS */
+
+#define TEGRA30_AHUB_I2S_INT_STATUS 0xc8
+
+/* TEGRA30_AHUB_DAM_INT_STATUS */
+
+#define TEGRA30_AHUB_DAM_INT_STATUS 0xcc
+
+/* TEGRA30_AHUB_SPDIF_INT_STATUS */
+
+#define TEGRA30_AHUB_SPDIF_INT_STATUS 0xd4
+
+/* TEGRA30_AHUB_APBIF_INT_STATUS */
+
+#define TEGRA30_AHUB_APBIF_INT_STATUS 0xd8
+
+/* TEGRA30_AHUB_I2S_INT_SOURCE */
+
+#define TEGRA30_AHUB_I2S_INT_SOURCE 0xe0
+
+/* TEGRA30_AHUB_DAM_INT_SOURCE */
+
+#define TEGRA30_AHUB_DAM_INT_SOURCE 0xe4
+
+/* TEGRA30_AHUB_SPDIF_INT_SOURCE */
+
+#define TEGRA30_AHUB_SPDIF_INT_SOURCE 0xec
+
+/* TEGRA30_AHUB_APBIF_INT_SOURCE */
+
+#define TEGRA30_AHUB_APBIF_INT_SOURCE 0xf0
+
+/* TEGRA30_AHUB_I2S_INT_SET */
+
+#define TEGRA30_AHUB_I2S_INT_SET 0xf8
+
+/* TEGRA30_AHUB_DAM_INT_SET */
+
+#define TEGRA30_AHUB_DAM_INT_SET 0xfc
+
+/* TEGRA30_AHUB_SPDIF_INT_SET */
+
+#define TEGRA30_AHUB_SPDIF_INT_SET 0x100
+
+/* TEGRA30_AHUB_APBIF_INT_SET */
+
+#define TEGRA30_AHUB_APBIF_INT_SET 0x104
+
+/* Registers within TEGRA30_AHUB_BASE */
+
+#define TEGRA30_AHUB_AUDIO_RX 0x0
+#define TEGRA30_AHUB_AUDIO_RX_STRIDE 0x4
+#define TEGRA30_AHUB_AUDIO_RX_COUNT 17
+/* This register repeats once for each entry in enum tegra30_ahub_rxcif */
+/* The fields in this register are 1 bit per entry in tegra30_ahub_txcif */
+
+/*
+ * Terminology:
+ * AHUB: Audio Hub; a cross-bar switch between the audio devices: DMA FIFOs,
+ * I2S controllers, SPDIF controllers, and DAMs.
+ * XBAR: The core cross-bar component of the AHUB.
+ * CIF: Client Interface; the HW module connecting an audio device to the
+ * XBAR.
+ * DAM: Digital Audio Mixer: A HW module that mixes multiple audio streams,
+ * possibly including sample-rate conversion.
+ *
+ * Each TX CIF transmits data into the XBAR. Each RX CIF can receive audio
+ * transmitted by a particular TX CIF.
+ *
+ * This driver is currently very simplistic; many HW features are not
+ * exposed; DAMs are not supported, only 16-bit stereo audio is supported,
+ * etc.
+ */
+
+enum tegra30_ahub_txcif {
+ TEGRA30_AHUB_TXCIF_APBIF_TX0,
+ TEGRA30_AHUB_TXCIF_APBIF_TX1,
+ TEGRA30_AHUB_TXCIF_APBIF_TX2,
+ TEGRA30_AHUB_TXCIF_APBIF_TX3,
+ TEGRA30_AHUB_TXCIF_I2S0_TX0,
+ TEGRA30_AHUB_TXCIF_I2S1_TX0,
+ TEGRA30_AHUB_TXCIF_I2S2_TX0,
+ TEGRA30_AHUB_TXCIF_I2S3_TX0,
+ TEGRA30_AHUB_TXCIF_I2S4_TX0,
+ TEGRA30_AHUB_TXCIF_DAM0_TX0,
+ TEGRA30_AHUB_TXCIF_DAM1_TX0,
+ TEGRA30_AHUB_TXCIF_DAM2_TX0,
+ TEGRA30_AHUB_TXCIF_SPDIF_TX0,
+ TEGRA30_AHUB_TXCIF_SPDIF_TX1,
+};
+
+enum tegra30_ahub_rxcif {
+ TEGRA30_AHUB_RXCIF_APBIF_RX0,
+ TEGRA30_AHUB_RXCIF_APBIF_RX1,
+ TEGRA30_AHUB_RXcIF_APBIF_RX2,
+ TEGRA30_AHUB_RXCIF_APBIF_RX3,
+ TEGRA30_AHUB_RXCIF_I2S0_RX0,
+ TEGRA30_AHUB_RXCIF_I2S1_RX0,
+ TEGRA30_AHUB_RXCIF_I2S2_RX0,
+ TEGRA30_AHUB_RXCIF_I2S3_RX0,
+ TEGRA30_AHUB_RXCIF_I2S4_RX0,
+ TEGRA30_AHUB_RXCIF_DAM0_RX0,
+ TEGRA30_AHUB_RXCIF_DAM0_RX1,
+ TEGRA30_AHUB_RXCIF_DAM1_RX0,
+ TEGRA30_AHUB_RXCIF_DAM2_RX1,
+ TEGRA30_AHUB_RXCIF_DAM3_RX0,
+ TEGRA30_AHUB_RXCIF_DAM3_RX1,
+ TEGRA30_AHUB_RXCIF_SPDIF_RX0,
+ TEGRA30_AHUB_RXCIF_SPDIF_RX1,
+};
+
+extern int tegra30_ahub_allocate_rx_fifo(enum tegra30_ahub_rxcif *rxcif,
+ char *dmachan, int dmachan_len,
+ dma_addr_t *fiforeg);
+extern int tegra30_ahub_enable_rx_fifo(enum tegra30_ahub_rxcif rxcif);
+extern int tegra30_ahub_disable_rx_fifo(enum tegra30_ahub_rxcif rxcif);
+extern int tegra30_ahub_free_rx_fifo(enum tegra30_ahub_rxcif rxcif);
+
+extern int tegra30_ahub_allocate_tx_fifo(enum tegra30_ahub_txcif *txcif,
+ char *dmachan, int dmachan_len,
+ dma_addr_t *fiforeg);
+extern int tegra30_ahub_enable_tx_fifo(enum tegra30_ahub_txcif txcif);
+extern int tegra30_ahub_disable_tx_fifo(enum tegra30_ahub_txcif txcif);
+extern int tegra30_ahub_free_tx_fifo(enum tegra30_ahub_txcif txcif);
+
+extern int tegra30_ahub_set_rx_cif_source(enum tegra30_ahub_rxcif rxcif,
+ enum tegra30_ahub_txcif txcif);
+extern int tegra30_ahub_unset_rx_cif_source(enum tegra30_ahub_rxcif rxcif);
+
+struct tegra30_ahub_cif_conf {
+ unsigned int threshold;
+ unsigned int audio_channels;
+ unsigned int client_channels;
+ unsigned int audio_bits;
+ unsigned int client_bits;
+ unsigned int expand;
+ unsigned int stereo_conv;
+ unsigned int replicate;
+ unsigned int direction;
+ unsigned int truncate;
+ unsigned int mono_conv;
+};
+
+void tegra30_ahub_set_cif(struct regmap *regmap, unsigned int reg,
+ struct tegra30_ahub_cif_conf *conf);
+void tegra124_ahub_set_cif(struct regmap *regmap, unsigned int reg,
+ struct tegra30_ahub_cif_conf *conf);
+
+struct tegra30_ahub_soc_data {
+ unsigned int num_resets;
+ void (*set_audio_cif)(struct regmap *regmap,
+ unsigned int reg,
+ struct tegra30_ahub_cif_conf *conf);
+ /*
+ * FIXME: There are many more differences in HW, such as:
+ * - More APBIF channels.
+ * - Extra separate chunks of register address space to represent
+ * the extra APBIF channels.
+ * - More units connected to the AHUB, so that tegra30_ahub_[rt]xcif
+ * need expansion, coupled with there being more defined bits in
+ * the AHUB routing registers.
+ * However, the driver doesn't support those new features yet, so we
+ * don't represent them here yet.
+ */
+};
+
+struct tegra30_ahub {
+ const struct tegra30_ahub_soc_data *soc_data;
+ struct device *dev;
+ struct reset_control_bulk_data resets[21];
+ unsigned int nresets;
+ struct clk_bulk_data clocks[2];
+ unsigned int nclocks;
+ resource_size_t apbif_addr;
+ struct regmap *regmap_apbif;
+ struct regmap *regmap_ahub;
+ DECLARE_BITMAP(rx_usage, TEGRA30_AHUB_CHANNEL_CTRL_COUNT);
+ DECLARE_BITMAP(tx_usage, TEGRA30_AHUB_CHANNEL_CTRL_COUNT);
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c
new file mode 100644
index 000000000..10cd37096
--- /dev/null
+++ b/sound/soc/tegra/tegra30_i2s.c
@@ -0,0 +1,574 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tegra30_i2s.c - Tegra30 I2S driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (c) 2010-2012, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ */
+
+#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 <linux/reset.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "tegra30_ahub.h"
+#include "tegra30_i2s.h"
+
+#define DRV_NAME "tegra30-i2s"
+
+static __maybe_unused int tegra30_i2s_runtime_suspend(struct device *dev)
+{
+ struct tegra30_i2s *i2s = dev_get_drvdata(dev);
+
+ regcache_cache_only(i2s->regmap, true);
+
+ clk_disable_unprepare(i2s->clk_i2s);
+
+ return 0;
+}
+
+static __maybe_unused int tegra30_i2s_runtime_resume(struct device *dev)
+{
+ struct tegra30_i2s *i2s = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(i2s->clk_i2s);
+ if (ret) {
+ dev_err(dev, "clk_enable failed: %d\n", ret);
+ return ret;
+ }
+
+ regcache_cache_only(i2s->regmap, false);
+ regcache_mark_dirty(i2s->regmap);
+
+ ret = regcache_sync(i2s->regmap);
+ if (ret)
+ goto disable_clocks;
+
+ return 0;
+
+disable_clocks:
+ clk_disable_unprepare(i2s->clk_i2s);
+
+ return ret;
+}
+
+static int tegra30_i2s_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ unsigned int mask = 0, val = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mask |= TEGRA30_I2S_CTRL_MASTER_ENABLE;
+ 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_BC_FC:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mask |= TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK |
+ TEGRA30_I2S_CTRL_LRCK_MASK;
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC;
+ val |= TEGRA30_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC;
+ val |= TEGRA30_I2S_CTRL_LRCK_R_LOW;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK;
+ val |= TEGRA30_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK;
+ val |= TEGRA30_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK;
+ val |= TEGRA30_I2S_CTRL_LRCK_L_LOW;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pm_runtime_get_sync(dai->dev);
+ regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val);
+ pm_runtime_put(dai->dev);
+
+ return 0;
+}
+
+static int tegra30_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct device *dev = dai->dev;
+ struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ unsigned int mask, val, reg;
+ int ret, sample_size, srate, i2sclock, bitcnt;
+ struct tegra30_ahub_cif_conf cif_conf;
+
+ if (params_channels(params) != 2)
+ return -EINVAL;
+
+ mask = TEGRA30_I2S_CTRL_BIT_SIZE_MASK;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ val = TEGRA30_I2S_CTRL_BIT_SIZE_16;
+ sample_size = 16;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val);
+
+ srate = params_rate(params);
+
+ /* Final "* 2" required by Tegra hardware */
+ i2sclock = srate * params_channels(params) * sample_size * 2;
+
+ bitcnt = (i2sclock / (2 * srate)) - 1;
+ if (bitcnt < 0 || bitcnt > TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US)
+ return -EINVAL;
+
+ ret = clk_set_rate(i2s->clk_i2s, i2sclock);
+ if (ret) {
+ dev_err(dev, "Can't set I2S clock rate: %d\n", ret);
+ return ret;
+ }
+
+ val = bitcnt << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT;
+
+ if (i2sclock % (2 * srate))
+ val |= TEGRA30_I2S_TIMING_NON_SYM_ENABLE;
+
+ regmap_write(i2s->regmap, TEGRA30_I2S_TIMING, val);
+
+ cif_conf.threshold = 0;
+ cif_conf.audio_channels = 2;
+ cif_conf.client_channels = 2;
+ cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16;
+ cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16;
+ cif_conf.expand = 0;
+ cif_conf.stereo_conv = 0;
+ cif_conf.replicate = 0;
+ cif_conf.truncate = 0;
+ cif_conf.mono_conv = 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX;
+ reg = TEGRA30_I2S_CIF_RX_CTRL;
+ } else {
+ cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX;
+ reg = TEGRA30_I2S_CIF_TX_CTRL;
+ }
+
+ i2s->soc_data->set_audio_cif(i2s->regmap, reg, &cif_conf);
+
+ val = (1 << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) |
+ (1 << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT);
+ regmap_write(i2s->regmap, TEGRA30_I2S_OFFSET, val);
+
+ return 0;
+}
+
+static void tegra30_i2s_start_playback(struct tegra30_i2s *i2s)
+{
+ tegra30_ahub_enable_tx_fifo(i2s->playback_fifo_cif);
+ regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL,
+ TEGRA30_I2S_CTRL_XFER_EN_TX,
+ TEGRA30_I2S_CTRL_XFER_EN_TX);
+}
+
+static void tegra30_i2s_stop_playback(struct tegra30_i2s *i2s)
+{
+ tegra30_ahub_disable_tx_fifo(i2s->playback_fifo_cif);
+ regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL,
+ TEGRA30_I2S_CTRL_XFER_EN_TX, 0);
+}
+
+static void tegra30_i2s_start_capture(struct tegra30_i2s *i2s)
+{
+ tegra30_ahub_enable_rx_fifo(i2s->capture_fifo_cif);
+ regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL,
+ TEGRA30_I2S_CTRL_XFER_EN_RX,
+ TEGRA30_I2S_CTRL_XFER_EN_RX);
+}
+
+static void tegra30_i2s_stop_capture(struct tegra30_i2s *i2s)
+{
+ regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL,
+ TEGRA30_I2S_CTRL_XFER_EN_RX, 0);
+ tegra30_ahub_disable_rx_fifo(i2s->capture_fifo_cif);
+}
+
+static int tegra30_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tegra30_i2s_start_playback(i2s);
+ else
+ tegra30_i2s_start_capture(i2s);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tegra30_i2s_stop_playback(i2s);
+ else
+ tegra30_i2s_stop_capture(i2s);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tegra30_i2s_set_tdm(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int slots, int slot_width)
+{
+ struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ unsigned int mask, val;
+
+ dev_dbg(dai->dev, "%s: txmask=0x%08x rxmask=0x%08x slots=%d width=%d\n",
+ __func__, tx_mask, rx_mask, slots, slot_width);
+
+ mask = TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK |
+ TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK |
+ TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK;
+
+ val = (tx_mask << TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT) |
+ (rx_mask << TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT) |
+ ((slots - 1) << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT);
+
+ pm_runtime_get_sync(dai->dev);
+ regmap_update_bits(i2s->regmap, TEGRA30_I2S_SLOT_CTRL, mask, val);
+ /* set the fsync width to minimum of 1 clock width */
+ regmap_update_bits(i2s->regmap, TEGRA30_I2S_CH_CTRL,
+ TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK, 0x0);
+ pm_runtime_put(dai->dev);
+
+ return 0;
+}
+
+static int tegra30_i2s_probe(struct snd_soc_dai *dai)
+{
+ struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ dai->capture_dma_data = &i2s->capture_dma_data;
+ dai->playback_dma_data = &i2s->playback_dma_data;
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops tegra30_i2s_dai_ops = {
+ .set_fmt = tegra30_i2s_set_fmt,
+ .hw_params = tegra30_i2s_hw_params,
+ .trigger = tegra30_i2s_trigger,
+ .set_tdm_slot = tegra30_i2s_set_tdm,
+};
+
+static const struct snd_soc_dai_driver tegra30_i2s_dai_template = {
+ .probe = tegra30_i2s_probe,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra30_i2s_dai_ops,
+ .symmetric_rate = 1,
+};
+
+static const struct snd_soc_component_driver tegra30_i2s_component = {
+ .name = DRV_NAME,
+ .legacy_dai_naming = 1,
+};
+
+static bool tegra30_i2s_wr_rd_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA30_I2S_CTRL:
+ case TEGRA30_I2S_TIMING:
+ case TEGRA30_I2S_OFFSET:
+ case TEGRA30_I2S_CH_CTRL:
+ case TEGRA30_I2S_SLOT_CTRL:
+ case TEGRA30_I2S_CIF_RX_CTRL:
+ case TEGRA30_I2S_CIF_TX_CTRL:
+ case TEGRA30_I2S_FLOWCTL:
+ case TEGRA30_I2S_TX_STEP:
+ case TEGRA30_I2S_FLOW_STATUS:
+ case TEGRA30_I2S_FLOW_TOTAL:
+ case TEGRA30_I2S_FLOW_OVER:
+ case TEGRA30_I2S_FLOW_UNDER:
+ case TEGRA30_I2S_LCOEF_1_4_0:
+ case TEGRA30_I2S_LCOEF_1_4_1:
+ case TEGRA30_I2S_LCOEF_1_4_2:
+ case TEGRA30_I2S_LCOEF_1_4_3:
+ case TEGRA30_I2S_LCOEF_1_4_4:
+ case TEGRA30_I2S_LCOEF_1_4_5:
+ case TEGRA30_I2S_LCOEF_2_4_0:
+ case TEGRA30_I2S_LCOEF_2_4_1:
+ case TEGRA30_I2S_LCOEF_2_4_2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tegra30_i2s_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA30_I2S_FLOW_STATUS:
+ case TEGRA30_I2S_FLOW_TOTAL:
+ case TEGRA30_I2S_FLOW_OVER:
+ case TEGRA30_I2S_FLOW_UNDER:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tegra30_i2s_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA30_I2S_LCOEF_2_4_2,
+ .writeable_reg = tegra30_i2s_wr_rd_reg,
+ .readable_reg = tegra30_i2s_wr_rd_reg,
+ .volatile_reg = tegra30_i2s_volatile_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct tegra30_i2s_soc_data tegra30_i2s_config = {
+ .set_audio_cif = tegra30_ahub_set_cif,
+};
+
+static const struct tegra30_i2s_soc_data tegra124_i2s_config = {
+ .set_audio_cif = tegra124_ahub_set_cif,
+};
+
+static const struct of_device_id tegra30_i2s_of_match[] = {
+ { .compatible = "nvidia,tegra124-i2s", .data = &tegra124_i2s_config },
+ { .compatible = "nvidia,tegra30-i2s", .data = &tegra30_i2s_config },
+ {},
+};
+
+static int tegra30_i2s_platform_probe(struct platform_device *pdev)
+{
+ struct tegra30_i2s *i2s;
+ const struct tegra30_i2s_soc_data *soc_data;
+ u32 cif_ids[2];
+ void __iomem *regs;
+ int ret;
+
+ i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_i2s), GFP_KERNEL);
+ if (!i2s) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ dev_set_drvdata(&pdev->dev, i2s);
+
+ soc_data = of_device_get_match_data(&pdev->dev);
+ if (!soc_data) {
+ dev_err(&pdev->dev, "Error: No device match found\n");
+ ret = -ENODEV;
+ goto err;
+ }
+ i2s->soc_data = soc_data;
+
+ i2s->dai = tegra30_i2s_dai_template;
+ i2s->dai.name = dev_name(&pdev->dev);
+
+ ret = of_property_read_u32_array(pdev->dev.of_node,
+ "nvidia,ahub-cif-ids", cif_ids,
+ ARRAY_SIZE(cif_ids));
+ if (ret < 0)
+ goto err;
+
+ i2s->playback_i2s_cif = cif_ids[0];
+ i2s->capture_i2s_cif = cif_ids[1];
+
+ i2s->clk_i2s = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(i2s->clk_i2s)) {
+ dev_err(&pdev->dev, "Can't retrieve i2s clock\n");
+ ret = PTR_ERR(i2s->clk_i2s);
+ goto err;
+ }
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs)) {
+ ret = PTR_ERR(regs);
+ goto err;
+ }
+
+ i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
+ &tegra30_i2s_regmap_config);
+ if (IS_ERR(i2s->regmap)) {
+ dev_err(&pdev->dev, "regmap init failed\n");
+ ret = PTR_ERR(i2s->regmap);
+ goto err;
+ }
+ regcache_cache_only(i2s->regmap, true);
+
+ pm_runtime_enable(&pdev->dev);
+
+ i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ i2s->playback_dma_data.maxburst = 4;
+ ret = tegra30_ahub_allocate_tx_fifo(&i2s->playback_fifo_cif,
+ i2s->playback_dma_chan,
+ sizeof(i2s->playback_dma_chan),
+ &i2s->playback_dma_data.addr);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not alloc TX FIFO: %d\n", ret);
+ goto err_pm_disable;
+ }
+ ret = tegra30_ahub_set_rx_cif_source(i2s->playback_i2s_cif,
+ i2s->playback_fifo_cif);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not route TX FIFO: %d\n", ret);
+ goto err_free_tx_fifo;
+ }
+
+ i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ i2s->capture_dma_data.maxburst = 4;
+ ret = tegra30_ahub_allocate_rx_fifo(&i2s->capture_fifo_cif,
+ i2s->capture_dma_chan,
+ sizeof(i2s->capture_dma_chan),
+ &i2s->capture_dma_data.addr);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not alloc RX FIFO: %d\n", ret);
+ goto err_unroute_tx_fifo;
+ }
+ ret = tegra30_ahub_set_rx_cif_source(i2s->capture_fifo_cif,
+ i2s->capture_i2s_cif);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not route TX FIFO: %d\n", ret);
+ goto err_free_rx_fifo;
+ }
+
+ ret = snd_soc_register_component(&pdev->dev, &tegra30_i2s_component,
+ &i2s->dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
+ ret = -ENOMEM;
+ goto err_unroute_rx_fifo;
+ }
+
+ ret = tegra_pcm_platform_register_with_chan_names(&pdev->dev,
+ &i2s->dma_config, i2s->playback_dma_chan,
+ i2s->capture_dma_chan);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
+ goto err_unregister_component;
+ }
+
+ return 0;
+
+err_unregister_component:
+ snd_soc_unregister_component(&pdev->dev);
+err_unroute_rx_fifo:
+ tegra30_ahub_unset_rx_cif_source(i2s->capture_fifo_cif);
+err_free_rx_fifo:
+ tegra30_ahub_free_rx_fifo(i2s->capture_fifo_cif);
+err_unroute_tx_fifo:
+ tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif);
+err_free_tx_fifo:
+ tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif);
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+err:
+ return ret;
+}
+
+static int tegra30_i2s_platform_remove(struct platform_device *pdev)
+{
+ struct tegra30_i2s *i2s = dev_get_drvdata(&pdev->dev);
+
+ tegra_pcm_platform_unregister(&pdev->dev);
+ snd_soc_unregister_component(&pdev->dev);
+
+ tegra30_ahub_unset_rx_cif_source(i2s->capture_fifo_cif);
+ tegra30_ahub_free_rx_fifo(i2s->capture_fifo_cif);
+
+ tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif);
+ tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif);
+
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra30_i2s_pm_ops = {
+ SET_RUNTIME_PM_OPS(tegra30_i2s_runtime_suspend,
+ tegra30_i2s_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver tegra30_i2s_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = tegra30_i2s_of_match,
+ .pm = &tegra30_i2s_pm_ops,
+ },
+ .probe = tegra30_i2s_platform_probe,
+ .remove = tegra30_i2s_platform_remove,
+};
+module_platform_driver(tegra30_i2s_driver);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra30 I2S ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_DEVICE_TABLE(of, tegra30_i2s_of_match);
diff --git a/sound/soc/tegra/tegra30_i2s.h b/sound/soc/tegra/tegra30_i2s.h
new file mode 100644
index 000000000..0b1f3125a
--- /dev/null
+++ b/sound/soc/tegra/tegra30_i2s.h
@@ -0,0 +1,240 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra30_i2s.h - Definitions for Tegra30 I2S driver
+ *
+ * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved.
+ */
+
+#ifndef __TEGRA30_I2S_H__
+#define __TEGRA30_I2S_H__
+
+#include "tegra_pcm.h"
+
+/* Register offsets from TEGRA30_I2S*_BASE */
+
+#define TEGRA30_I2S_CTRL 0x0
+#define TEGRA30_I2S_TIMING 0x4
+#define TEGRA30_I2S_OFFSET 0x08
+#define TEGRA30_I2S_CH_CTRL 0x0c
+#define TEGRA30_I2S_SLOT_CTRL 0x10
+#define TEGRA30_I2S_CIF_RX_CTRL 0x14
+#define TEGRA30_I2S_CIF_TX_CTRL 0x18
+#define TEGRA30_I2S_FLOWCTL 0x1c
+#define TEGRA30_I2S_TX_STEP 0x20
+#define TEGRA30_I2S_FLOW_STATUS 0x24
+#define TEGRA30_I2S_FLOW_TOTAL 0x28
+#define TEGRA30_I2S_FLOW_OVER 0x2c
+#define TEGRA30_I2S_FLOW_UNDER 0x30
+#define TEGRA30_I2S_LCOEF_1_4_0 0x34
+#define TEGRA30_I2S_LCOEF_1_4_1 0x38
+#define TEGRA30_I2S_LCOEF_1_4_2 0x3c
+#define TEGRA30_I2S_LCOEF_1_4_3 0x40
+#define TEGRA30_I2S_LCOEF_1_4_4 0x44
+#define TEGRA30_I2S_LCOEF_1_4_5 0x48
+#define TEGRA30_I2S_LCOEF_2_4_0 0x4c
+#define TEGRA30_I2S_LCOEF_2_4_1 0x50
+#define TEGRA30_I2S_LCOEF_2_4_2 0x54
+
+/* Fields in TEGRA30_I2S_CTRL */
+
+#define TEGRA30_I2S_CTRL_XFER_EN_TX (1 << 31)
+#define TEGRA30_I2S_CTRL_XFER_EN_RX (1 << 30)
+#define TEGRA30_I2S_CTRL_CG_EN (1 << 29)
+#define TEGRA30_I2S_CTRL_SOFT_RESET (1 << 28)
+#define TEGRA30_I2S_CTRL_TX_FLOWCTL_EN (1 << 27)
+
+#define TEGRA30_I2S_CTRL_OBS_SEL_SHIFT 24
+#define TEGRA30_I2S_CTRL_OBS_SEL_MASK (7 << TEGRA30_I2S_CTRL_OBS_SEL_SHIFT)
+
+#define TEGRA30_I2S_FRAME_FORMAT_LRCK 0
+#define TEGRA30_I2S_FRAME_FORMAT_FSYNC 1
+
+#define TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT 12
+#define TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK (7 << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT)
+#define TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK (TEGRA30_I2S_FRAME_FORMAT_LRCK << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT)
+#define TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC (TEGRA30_I2S_FRAME_FORMAT_FSYNC << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT)
+
+#define TEGRA30_I2S_CTRL_MASTER_ENABLE (1 << 10)
+
+#define TEGRA30_I2S_LRCK_LEFT_LOW 0
+#define TEGRA30_I2S_LRCK_RIGHT_LOW 1
+
+#define TEGRA30_I2S_CTRL_LRCK_SHIFT 9
+#define TEGRA30_I2S_CTRL_LRCK_MASK (1 << TEGRA30_I2S_CTRL_LRCK_SHIFT)
+#define TEGRA30_I2S_CTRL_LRCK_L_LOW (TEGRA30_I2S_LRCK_LEFT_LOW << TEGRA30_I2S_CTRL_LRCK_SHIFT)
+#define TEGRA30_I2S_CTRL_LRCK_R_LOW (TEGRA30_I2S_LRCK_RIGHT_LOW << TEGRA30_I2S_CTRL_LRCK_SHIFT)
+
+#define TEGRA30_I2S_CTRL_LPBK_ENABLE (1 << 8)
+
+#define TEGRA30_I2S_BIT_CODE_LINEAR 0
+#define TEGRA30_I2S_BIT_CODE_ULAW 1
+#define TEGRA30_I2S_BIT_CODE_ALAW 2
+
+#define TEGRA30_I2S_CTRL_BIT_CODE_SHIFT 4
+#define TEGRA30_I2S_CTRL_BIT_CODE_MASK (3 << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT)
+#define TEGRA30_I2S_CTRL_BIT_CODE_LINEAR (TEGRA30_I2S_BIT_CODE_LINEAR << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT)
+#define TEGRA30_I2S_CTRL_BIT_CODE_ULAW (TEGRA30_I2S_BIT_CODE_ULAW << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT)
+#define TEGRA30_I2S_CTRL_BIT_CODE_ALAW (TEGRA30_I2S_BIT_CODE_ALAW << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT)
+
+#define TEGRA30_I2S_BITS_8 1
+#define TEGRA30_I2S_BITS_12 2
+#define TEGRA30_I2S_BITS_16 3
+#define TEGRA30_I2S_BITS_20 4
+#define TEGRA30_I2S_BITS_24 5
+#define TEGRA30_I2S_BITS_28 6
+#define TEGRA30_I2S_BITS_32 7
+
+/* Sample container size; see {RX,TX}_MASK field in CH_CTRL below */
+#define TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT 0
+#define TEGRA30_I2S_CTRL_BIT_SIZE_MASK (7 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA30_I2S_CTRL_BIT_SIZE_8 (TEGRA30_I2S_BITS_8 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA30_I2S_CTRL_BIT_SIZE_12 (TEGRA30_I2S_BITS_12 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA30_I2S_CTRL_BIT_SIZE_16 (TEGRA30_I2S_BITS_16 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA30_I2S_CTRL_BIT_SIZE_20 (TEGRA30_I2S_BITS_20 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA30_I2S_CTRL_BIT_SIZE_24 (TEGRA30_I2S_BITS_24 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA30_I2S_CTRL_BIT_SIZE_28 (TEGRA30_I2S_BITS_28 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA30_I2S_CTRL_BIT_SIZE_32 (TEGRA30_I2S_BITS_32 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT)
+
+/* Fields in TEGRA30_I2S_TIMING */
+
+#define TEGRA30_I2S_TIMING_NON_SYM_ENABLE (1 << 12)
+#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0
+#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7ff
+#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT)
+
+/* Fields in TEGRA30_I2S_OFFSET */
+
+#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT 16
+#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US 0x7ff
+#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK (TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT)
+#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT 0
+#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US 0x7ff
+#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK (TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT)
+
+/* Fields in TEGRA30_I2S_CH_CTRL */
+
+/* (FSYNC width - 1) in bit clocks */
+#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT 24
+#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US 0xff
+#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK (TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US << TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT)
+
+#define TEGRA30_I2S_HIGHZ_NO 0
+#define TEGRA30_I2S_HIGHZ_YES 1
+#define TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK 2
+
+#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT 12
+#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_MASK (3 << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT)
+#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_NO (TEGRA30_I2S_HIGHZ_NO << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT)
+#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_YES (TEGRA30_I2S_HIGHZ_YES << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT)
+#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_ON_HALF_BIT_CLK (TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT)
+
+#define TEGRA30_I2S_MSB_FIRST 0
+#define TEGRA30_I2S_LSB_FIRST 1
+
+#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT 10
+#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MASK (1 << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT)
+#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MSB_FIRST (TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT)
+#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_LSB_FIRST (TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT)
+#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT 9
+#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MASK (1 << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT)
+#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MSB_FIRST (TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT)
+#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_LSB_FIRST (TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT)
+
+#define TEGRA30_I2S_POS_EDGE 0
+#define TEGRA30_I2S_NEG_EDGE 1
+
+#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT 8
+#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_MASK (1 << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT)
+#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_POS_EDGE (TEGRA30_I2S_POS_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT)
+#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_NEG_EDGE (TEGRA30_I2S_NEG_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT)
+
+/* Sample size is # bits from BIT_SIZE minus this field */
+#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT 4
+#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US 7
+#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK (TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT)
+
+#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT 0
+#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US 7
+#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK (TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT)
+
+/* Fields in TEGRA30_I2S_SLOT_CTRL */
+
+/* Number of slots in frame, minus 1 */
+#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT 16
+#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK_US 7
+#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK (TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK_US << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT)
+
+/* TDM mode slot enable bitmask */
+#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT 8
+#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK (0xff << TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT)
+
+#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT 0
+#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK (0xff << TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT)
+
+/* Fields in TEGRA30_I2S_CIF_RX_CTRL */
+/* Uses field from TEGRA30_AUDIOCIF_CTRL_* in tegra30_ahub.h */
+
+/* Fields in TEGRA30_I2S_CIF_TX_CTRL */
+/* Uses field from TEGRA30_AUDIOCIF_CTRL_* in tegra30_ahub.h */
+
+/* Fields in TEGRA30_I2S_FLOWCTL */
+
+#define TEGRA30_I2S_FILTER_LINEAR 0
+#define TEGRA30_I2S_FILTER_QUAD 1
+
+#define TEGRA30_I2S_FLOWCTL_FILTER_SHIFT 31
+#define TEGRA30_I2S_FLOWCTL_FILTER_MASK (1 << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT)
+#define TEGRA30_I2S_FLOWCTL_FILTER_LINEAR (TEGRA30_I2S_FILTER_LINEAR << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT)
+#define TEGRA30_I2S_FLOWCTL_FILTER_QUAD (TEGRA30_I2S_FILTER_QUAD << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT)
+
+/* Fields in TEGRA30_I2S_TX_STEP */
+
+#define TEGRA30_I2S_TX_STEP_SHIFT 0
+#define TEGRA30_I2S_TX_STEP_MASK_US 0xffff
+#define TEGRA30_I2S_TX_STEP_MASK (TEGRA30_I2S_TX_STEP_MASK_US << TEGRA30_I2S_TX_STEP_SHIFT)
+
+/* Fields in TEGRA30_I2S_FLOW_STATUS */
+
+#define TEGRA30_I2S_FLOW_STATUS_UNDERFLOW (1 << 31)
+#define TEGRA30_I2S_FLOW_STATUS_OVERFLOW (1 << 30)
+#define TEGRA30_I2S_FLOW_STATUS_MONITOR_INT_EN (1 << 4)
+#define TEGRA30_I2S_FLOW_STATUS_COUNTER_CLR (1 << 3)
+#define TEGRA30_I2S_FLOW_STATUS_MONITOR_CLR (1 << 2)
+#define TEGRA30_I2S_FLOW_STATUS_COUNTER_EN (1 << 1)
+#define TEGRA30_I2S_FLOW_STATUS_MONITOR_EN (1 << 0)
+
+/*
+ * There are no fields in TEGRA30_I2S_FLOW_TOTAL, TEGRA30_I2S_FLOW_OVER,
+ * TEGRA30_I2S_FLOW_UNDER; they are counters taking the whole register.
+ */
+
+/* Fields in TEGRA30_I2S_LCOEF_* */
+
+#define TEGRA30_I2S_LCOEF_COEF_SHIFT 0
+#define TEGRA30_I2S_LCOEF_COEF_MASK_US 0xffff
+#define TEGRA30_I2S_LCOEF_COEF_MASK (TEGRA30_I2S_LCOEF_COEF_MASK_US << TEGRA30_I2S_LCOEF_COEF_SHIFT)
+
+struct tegra30_i2s_soc_data {
+ void (*set_audio_cif)(struct regmap *regmap,
+ unsigned int reg,
+ struct tegra30_ahub_cif_conf *conf);
+};
+
+struct tegra30_i2s {
+ const struct tegra30_i2s_soc_data *soc_data;
+ struct snd_soc_dai_driver dai;
+ int cif_id;
+ struct clk *clk_i2s;
+ enum tegra30_ahub_txcif capture_i2s_cif;
+ enum tegra30_ahub_rxcif capture_fifo_cif;
+ char capture_dma_chan[8];
+ struct snd_dmaengine_dai_dma_data capture_dma_data;
+ enum tegra30_ahub_rxcif playback_i2s_cif;
+ enum tegra30_ahub_txcif playback_fifo_cif;
+ char playback_dma_chan[8];
+ struct snd_dmaengine_dai_dma_data playback_dma_data;
+ struct regmap *regmap;
+ struct snd_dmaengine_pcm_config dma_config;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra_asoc_machine.c b/sound/soc/tegra/tegra_asoc_machine.c
new file mode 100644
index 000000000..78faa8bca
--- /dev/null
+++ b/sound/soc/tegra/tegra_asoc_machine.c
@@ -0,0 +1,903 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tegra_asoc_machine.c - Universal ASoC machine driver for NVIDIA Tegra boards.
+ */
+
+#include <linux/clk.h>
+#include <linux/export.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "tegra_asoc_machine.h"
+
+/* Headphones Jack */
+
+static struct snd_soc_jack tegra_machine_hp_jack;
+
+static struct snd_soc_jack_pin tegra_machine_hp_jack_pins[] = {
+ { .pin = "Headphone", .mask = SND_JACK_HEADPHONE },
+ { .pin = "Headphones", .mask = SND_JACK_HEADPHONE },
+};
+
+static struct snd_soc_jack_gpio tegra_machine_hp_jack_gpio = {
+ .name = "Headphones detection",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 150,
+};
+
+/* Headset Jack */
+
+static struct snd_soc_jack tegra_machine_headset_jack;
+
+static struct snd_soc_jack_pin tegra_machine_headset_jack_pins[] = {
+ { .pin = "Headset Mic", .mask = SND_JACK_MICROPHONE },
+ { .pin = "Headset Stereophone", .mask = SND_JACK_HEADPHONE },
+};
+
+static struct snd_soc_jack_gpio tegra_machine_headset_jack_gpio = {
+ .name = "Headset detection",
+ .report = SND_JACK_HEADSET,
+ .debounce_time = 150,
+};
+
+/* Mic Jack */
+
+static struct snd_soc_jack tegra_machine_mic_jack;
+
+static struct snd_soc_jack_pin tegra_machine_mic_jack_pins[] = {
+ { .pin = "Mic Jack", .mask = SND_JACK_MICROPHONE },
+ { .pin = "Headset Mic", .mask = SND_JACK_MICROPHONE },
+};
+
+static struct snd_soc_jack_gpio tegra_machine_mic_jack_gpio = {
+ .name = "Mic detection",
+ .report = SND_JACK_MICROPHONE,
+ .debounce_time = 150,
+};
+
+static int tegra_machine_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct tegra_machine *machine = snd_soc_card_get_drvdata(dapm->card);
+
+ if (!strcmp(w->name, "Int Spk") || !strcmp(w->name, "Speakers"))
+ gpiod_set_value_cansleep(machine->gpiod_spkr_en,
+ SND_SOC_DAPM_EVENT_ON(event));
+
+ if (!strcmp(w->name, "Mic Jack"))
+ gpiod_set_value_cansleep(machine->gpiod_ext_mic_en,
+ SND_SOC_DAPM_EVENT_ON(event));
+
+ if (!strcmp(w->name, "Int Mic"))
+ gpiod_set_value_cansleep(machine->gpiod_int_mic_en,
+ SND_SOC_DAPM_EVENT_ON(event));
+
+ if (!strcmp(w->name, "Headphone") || !strcmp(w->name, "Headphone Jack"))
+ gpiod_set_value_cansleep(machine->gpiod_hp_mute,
+ !SND_SOC_DAPM_EVENT_ON(event));
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget tegra_machine_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", tegra_machine_event),
+ SND_SOC_DAPM_HP("Headphone", tegra_machine_event),
+ SND_SOC_DAPM_HP("Headset Stereophone", NULL),
+ SND_SOC_DAPM_HP("Headphones", NULL),
+ SND_SOC_DAPM_SPK("Speakers", tegra_machine_event),
+ SND_SOC_DAPM_SPK("Int Spk", tegra_machine_event),
+ SND_SOC_DAPM_MIC("Int Mic", tegra_machine_event),
+ SND_SOC_DAPM_MIC("Mic Jack", tegra_machine_event),
+ SND_SOC_DAPM_MIC("Internal Mic 1", NULL),
+ SND_SOC_DAPM_MIC("Internal Mic 2", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Digital Mic", NULL),
+ SND_SOC_DAPM_MIC("Mic", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_LINE("LineIn", NULL),
+};
+
+static const struct snd_kcontrol_new tegra_machine_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speakers"),
+ SOC_DAPM_PIN_SWITCH("Int Spk"),
+ SOC_DAPM_PIN_SWITCH("Int Mic"),
+ 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) {
+ 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);
+ return err;
+ }
+
+ tegra_machine_hp_jack_gpio.desc = machine->gpiod_hp_det;
+
+ err = snd_soc_jack_add_gpios(&tegra_machine_hp_jack, 1,
+ &tegra_machine_hp_jack_gpio);
+ if (err)
+ dev_err(rtd->dev, "HP GPIOs not added: %d\n", err);
+ }
+
+ if (machine->gpiod_hp_det && machine->asoc->add_headset_jack) {
+ 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);
+ return err;
+ }
+
+ tegra_machine_headset_jack_gpio.desc = machine->gpiod_hp_det;
+
+ err = snd_soc_jack_add_gpios(&tegra_machine_headset_jack, 1,
+ &tegra_machine_headset_jack_gpio);
+ if (err)
+ dev_err(rtd->dev, "Headset GPIOs not added: %d\n", err);
+ }
+
+ if (machine->gpiod_mic_det && machine->asoc->add_mic_jack) {
+ 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;
+ }
+
+ tegra_machine_mic_jack_gpio.desc = machine->gpiod_mic_det;
+
+ err = snd_soc_jack_add_gpios(&tegra_machine_mic_jack, 1,
+ &tegra_machine_mic_jack_gpio);
+ if (err)
+ dev_err(rtd->dev, "Mic GPIOs not added: %d\n", err);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_asoc_machine_init);
+
+static unsigned int tegra_machine_mclk_rate_128(unsigned int srate)
+{
+ return 128 * srate;
+}
+
+static unsigned int tegra_machine_mclk_rate_256(unsigned int srate)
+{
+ return 256 * srate;
+}
+
+static unsigned int tegra_machine_mclk_rate_512(unsigned int srate)
+{
+ return 512 * srate;
+}
+
+static unsigned int tegra_machine_mclk_rate_12mhz(unsigned int srate)
+{
+ unsigned int mclk;
+
+ switch (srate) {
+ case 8000:
+ case 16000:
+ case 24000:
+ case 32000:
+ case 48000:
+ case 64000:
+ case 96000:
+ mclk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ mclk = 11289600;
+ break;
+ default:
+ mclk = 12000000;
+ break;
+ }
+
+ return mclk;
+}
+
+static int tegra_machine_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_card *card = rtd->card;
+ struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
+ unsigned int srate = params_rate(params);
+ unsigned int mclk = machine->asoc->mclk_rate(srate);
+ unsigned int clk_id = machine->asoc->mclk_id;
+ unsigned int new_baseclock;
+ int err;
+
+ switch (srate) {
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ if (of_machine_is_compatible("nvidia,tegra20"))
+ new_baseclock = 56448000;
+ else if (of_machine_is_compatible("nvidia,tegra30"))
+ new_baseclock = 564480000;
+ else
+ new_baseclock = 282240000;
+ break;
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
+ case 64000:
+ case 96000:
+ if (of_machine_is_compatible("nvidia,tegra20"))
+ new_baseclock = 73728000;
+ else if (of_machine_is_compatible("nvidia,tegra30"))
+ new_baseclock = 552960000;
+ else
+ new_baseclock = 368640000;
+ break;
+ default:
+ dev_err(card->dev, "Invalid sound rate: %u\n", srate);
+ return -EINVAL;
+ }
+
+ if (new_baseclock != machine->set_baseclock ||
+ mclk != machine->set_mclk) {
+ machine->set_baseclock = 0;
+ machine->set_mclk = 0;
+
+ clk_disable_unprepare(machine->clk_cdev1);
+
+ err = clk_set_rate(machine->clk_pll_a, new_baseclock);
+ if (err) {
+ dev_err(card->dev, "Can't set pll_a rate: %d\n", err);
+ return err;
+ }
+
+ err = clk_set_rate(machine->clk_pll_a_out0, mclk);
+ if (err) {
+ dev_err(card->dev, "Can't set pll_a_out0 rate: %d\n", err);
+ return err;
+ }
+
+ /* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */
+
+ err = clk_prepare_enable(machine->clk_cdev1);
+ if (err) {
+ dev_err(card->dev, "Can't enable cdev1: %d\n", err);
+ return err;
+ }
+
+ machine->set_baseclock = new_baseclock;
+ machine->set_mclk = mclk;
+ }
+
+ err = snd_soc_dai_set_sysclk(codec_dai, clk_id, mclk, SND_SOC_CLOCK_IN);
+ if (err < 0) {
+ dev_err(card->dev, "codec_dai clock not set: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops tegra_machine_snd_ops = {
+ .hw_params = tegra_machine_hw_params,
+};
+
+static void tegra_machine_node_release(void *of_node)
+{
+ of_node_put(of_node);
+}
+
+static struct device_node *
+tegra_machine_parse_phandle(struct device *dev, const char *name)
+{
+ struct device_node *np;
+ int err;
+
+ np = of_parse_phandle(dev->of_node, name, 0);
+ if (!np) {
+ dev_err(dev, "Property '%s' missing or invalid\n", name);
+ return ERR_PTR(-EINVAL);
+ }
+
+ err = devm_add_action_or_reset(dev, tegra_machine_node_release, np);
+ if (err)
+ return ERR_PTR(err);
+
+ return np;
+}
+
+static void tegra_machine_unregister_codec(void *pdev)
+{
+ platform_device_unregister(pdev);
+}
+
+static int tegra_machine_register_codec(struct device *dev, const char *name)
+{
+ struct platform_device *pdev;
+ int err;
+
+ if (!name)
+ return 0;
+
+ pdev = platform_device_register_simple(name, -1, NULL, 0);
+ if (IS_ERR(pdev))
+ return PTR_ERR(pdev);
+
+ err = devm_add_action_or_reset(dev, tegra_machine_unregister_codec,
+ pdev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+int tegra_asoc_machine_probe(struct platform_device *pdev)
+{
+ struct device_node *np_codec, *np_i2s, *np_ac97;
+ const struct tegra_asoc_data *asoc;
+ struct device *dev = &pdev->dev;
+ struct tegra_machine *machine;
+ struct snd_soc_card *card;
+ struct gpio_desc *gpiod;
+ int err;
+
+ machine = devm_kzalloc(dev, sizeof(*machine), GFP_KERNEL);
+ if (!machine)
+ return -ENOMEM;
+
+ asoc = of_device_get_match_data(dev);
+ card = asoc->card;
+ card->dev = dev;
+
+ machine->asoc = asoc;
+ machine->mic_jack = &tegra_machine_mic_jack;
+ machine->hp_jack_gpio = &tegra_machine_hp_jack_gpio;
+ snd_soc_card_set_drvdata(card, machine);
+
+ gpiod = devm_gpiod_get_optional(dev, "nvidia,hp-mute", GPIOD_OUT_HIGH);
+ machine->gpiod_hp_mute = gpiod;
+ if (IS_ERR(gpiod))
+ return PTR_ERR(gpiod);
+
+ gpiod = devm_gpiod_get_optional(dev, "nvidia,hp-det", GPIOD_IN);
+ machine->gpiod_hp_det = gpiod;
+ if (IS_ERR(gpiod))
+ return PTR_ERR(gpiod);
+
+ gpiod = devm_gpiod_get_optional(dev, "nvidia,mic-det", GPIOD_IN);
+ machine->gpiod_mic_det = gpiod;
+ if (IS_ERR(gpiod))
+ return PTR_ERR(gpiod);
+
+ gpiod = devm_gpiod_get_optional(dev, "nvidia,spkr-en", GPIOD_OUT_LOW);
+ machine->gpiod_spkr_en = gpiod;
+ if (IS_ERR(gpiod))
+ return PTR_ERR(gpiod);
+
+ gpiod = devm_gpiod_get_optional(dev, "nvidia,int-mic-en", GPIOD_OUT_LOW);
+ machine->gpiod_int_mic_en = gpiod;
+ if (IS_ERR(gpiod))
+ return PTR_ERR(gpiod);
+
+ gpiod = devm_gpiod_get_optional(dev, "nvidia,ext-mic-en", GPIOD_OUT_LOW);
+ machine->gpiod_ext_mic_en = gpiod;
+ if (IS_ERR(gpiod))
+ return PTR_ERR(gpiod);
+
+ err = snd_soc_of_parse_card_name(card, "nvidia,model");
+ if (err)
+ return err;
+
+ if (!card->dapm_routes) {
+ err = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing");
+ if (err)
+ return err;
+ }
+
+ if (asoc->set_ac97) {
+ err = tegra_machine_register_codec(dev, asoc->codec_dev_name);
+ if (err)
+ return err;
+
+ np_ac97 = tegra_machine_parse_phandle(dev, "nvidia,ac97-controller");
+ if (IS_ERR(np_ac97))
+ return PTR_ERR(np_ac97);
+
+ card->dai_link->cpus->of_node = np_ac97;
+ card->dai_link->platforms->of_node = np_ac97;
+ } else {
+ np_codec = tegra_machine_parse_phandle(dev, "nvidia,audio-codec");
+ if (IS_ERR(np_codec))
+ return PTR_ERR(np_codec);
+
+ np_i2s = tegra_machine_parse_phandle(dev, "nvidia,i2s-controller");
+ if (IS_ERR(np_i2s))
+ return PTR_ERR(np_i2s);
+
+ card->dai_link->cpus->of_node = np_i2s;
+ card->dai_link->codecs->of_node = np_codec;
+ card->dai_link->platforms->of_node = np_i2s;
+ }
+
+ if (asoc->add_common_controls) {
+ card->controls = tegra_machine_controls;
+ card->num_controls = ARRAY_SIZE(tegra_machine_controls);
+ }
+
+ if (asoc->add_common_dapm_widgets) {
+ card->dapm_widgets = tegra_machine_dapm_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(tegra_machine_dapm_widgets);
+ }
+
+ if (asoc->add_common_snd_ops)
+ card->dai_link->ops = &tegra_machine_snd_ops;
+
+ if (!card->owner)
+ card->owner = THIS_MODULE;
+ if (!card->driver_name)
+ card->driver_name = "tegra";
+
+ machine->clk_pll_a = devm_clk_get(dev, "pll_a");
+ if (IS_ERR(machine->clk_pll_a)) {
+ dev_err(dev, "Can't retrieve clk pll_a\n");
+ return PTR_ERR(machine->clk_pll_a);
+ }
+
+ machine->clk_pll_a_out0 = devm_clk_get(dev, "pll_a_out0");
+ if (IS_ERR(machine->clk_pll_a_out0)) {
+ dev_err(dev, "Can't retrieve clk pll_a_out0\n");
+ return PTR_ERR(machine->clk_pll_a_out0);
+ }
+
+ machine->clk_cdev1 = devm_clk_get(dev, "mclk");
+ if (IS_ERR(machine->clk_cdev1)) {
+ dev_err(dev, "Can't retrieve clk cdev1\n");
+ return PTR_ERR(machine->clk_cdev1);
+ }
+
+ /*
+ * If clock parents are not set in DT, configure here to use clk_out_1
+ * as mclk and extern1 as parent for Tegra30 and higher.
+ */
+ if (!of_find_property(dev->of_node, "assigned-clock-parents", NULL) &&
+ !of_machine_is_compatible("nvidia,tegra20")) {
+ struct clk *clk_out_1, *clk_extern1;
+
+ dev_warn(dev, "Configuring clocks for a legacy device-tree\n");
+ dev_warn(dev, "Please update DT to use assigned-clock-parents\n");
+
+ clk_extern1 = devm_clk_get(dev, "extern1");
+ if (IS_ERR(clk_extern1)) {
+ dev_err(dev, "Can't retrieve clk extern1\n");
+ return PTR_ERR(clk_extern1);
+ }
+
+ err = clk_set_parent(clk_extern1, machine->clk_pll_a_out0);
+ if (err < 0) {
+ dev_err(dev, "Set parent failed for clk extern1\n");
+ return err;
+ }
+
+ clk_out_1 = devm_clk_get(dev, "pmc_clk_out_1");
+ if (IS_ERR(clk_out_1)) {
+ dev_err(dev, "Can't retrieve pmc_clk_out_1\n");
+ return PTR_ERR(clk_out_1);
+ }
+
+ err = clk_set_parent(clk_out_1, clk_extern1);
+ if (err < 0) {
+ dev_err(dev, "Set parent failed for pmc_clk_out_1\n");
+ return err;
+ }
+
+ machine->clk_cdev1 = clk_out_1;
+ }
+
+ if (asoc->set_ac97) {
+ /*
+ * AC97 rate is fixed at 24.576MHz and is used for both the
+ * host controller and the external codec
+ */
+ err = clk_set_rate(machine->clk_pll_a, 73728000);
+ if (err) {
+ dev_err(dev, "Can't set pll_a rate: %d\n", err);
+ return err;
+ }
+
+ err = clk_set_rate(machine->clk_pll_a_out0, 24576000);
+ if (err) {
+ dev_err(dev, "Can't set pll_a_out0 rate: %d\n", err);
+ return err;
+ }
+
+ machine->set_baseclock = 73728000;
+ machine->set_mclk = 24576000;
+ }
+
+ /*
+ * FIXME: There is some unknown dependency between audio MCLK disable
+ * and suspend-resume functionality on Tegra30, although audio MCLK is
+ * only needed for audio.
+ */
+ err = clk_prepare_enable(machine->clk_cdev1);
+ if (err) {
+ dev_err(dev, "Can't enable cdev1: %d\n", err);
+ return err;
+ }
+
+ err = devm_snd_soc_register_card(dev, card);
+ if (err)
+ return err;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_asoc_machine_probe);
+
+/* WM8753 machine */
+
+SND_SOC_DAILINK_DEFS(wm8753_hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8753-hifi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tegra_wm8753_dai = {
+ .name = "WM8753",
+ .stream_name = "WM8753 PCM",
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(wm8753_hifi),
+};
+
+static struct snd_soc_card snd_soc_tegra_wm8753 = {
+ .components = "codec:wm8753",
+ .dai_link = &tegra_wm8753_dai,
+ .num_links = 1,
+ .fully_routed = true,
+};
+
+static const struct tegra_asoc_data tegra_wm8753_data = {
+ .mclk_rate = tegra_machine_mclk_rate_12mhz,
+ .card = &snd_soc_tegra_wm8753,
+ .add_common_dapm_widgets = true,
+ .add_common_snd_ops = true,
+};
+
+/* WM9712 machine */
+
+static int tegra_wm9712_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "Mic Bias");
+}
+
+SND_SOC_DAILINK_DEFS(wm9712_hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-hifi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tegra_wm9712_dai = {
+ .name = "AC97 HiFi",
+ .stream_name = "AC97 HiFi",
+ .init = tegra_wm9712_init,
+ SND_SOC_DAILINK_REG(wm9712_hifi),
+};
+
+static struct snd_soc_card snd_soc_tegra_wm9712 = {
+ .components = "codec:wm9712",
+ .dai_link = &tegra_wm9712_dai,
+ .num_links = 1,
+ .fully_routed = true,
+};
+
+static const struct tegra_asoc_data tegra_wm9712_data = {
+ .card = &snd_soc_tegra_wm9712,
+ .add_common_dapm_widgets = true,
+ .codec_dev_name = "wm9712-codec",
+ .set_ac97 = true,
+};
+
+/* MAX98090 machine */
+
+SND_SOC_DAILINK_DEFS(max98090_hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "HiFi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tegra_max98090_dai = {
+ .name = "max98090",
+ .stream_name = "max98090 PCM",
+ .init = tegra_asoc_machine_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(max98090_hifi),
+};
+
+static struct snd_soc_card snd_soc_tegra_max98090 = {
+ .components = "codec:max98090",
+ .dai_link = &tegra_max98090_dai,
+ .num_links = 1,
+ .fully_routed = true,
+};
+
+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,
+ .add_mic_jack = true,
+ .add_hp_jack = true,
+};
+
+/* SGTL5000 machine */
+
+SND_SOC_DAILINK_DEFS(sgtl5000_hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "sgtl5000")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tegra_sgtl5000_dai = {
+ .name = "sgtl5000",
+ .stream_name = "HiFi",
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(sgtl5000_hifi),
+};
+
+static struct snd_soc_card snd_soc_tegra_sgtl5000 = {
+ .components = "codec:sgtl5000",
+ .dai_link = &tegra_sgtl5000_dai,
+ .num_links = 1,
+ .fully_routed = true,
+};
+
+static const struct tegra_asoc_data tegra_sgtl5000_data = {
+ .mclk_rate = tegra_machine_mclk_rate_12mhz,
+ .card = &snd_soc_tegra_sgtl5000,
+ .add_common_dapm_widgets = true,
+ .add_common_snd_ops = true,
+};
+
+/* TLV320AIC23 machine */
+
+static const struct snd_soc_dapm_widget trimslice_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Line Out", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+static const struct snd_soc_dapm_route trimslice_audio_map[] = {
+ {"Line Out", NULL, "LOUT"},
+ {"Line Out", NULL, "ROUT"},
+
+ {"LLINEIN", NULL, "Line In"},
+ {"RLINEIN", NULL, "Line In"},
+};
+
+SND_SOC_DAILINK_DEFS(tlv320aic23_hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "tlv320aic23-hifi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tegra_tlv320aic23_dai = {
+ .name = "TLV320AIC23",
+ .stream_name = "AIC23",
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(tlv320aic23_hifi),
+};
+
+static struct snd_soc_card snd_soc_tegra_trimslice = {
+ .name = "tegra-trimslice",
+ .components = "codec:tlv320aic23",
+ .dai_link = &tegra_tlv320aic23_dai,
+ .num_links = 1,
+ .dapm_widgets = trimslice_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(trimslice_dapm_widgets),
+ .dapm_routes = trimslice_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(trimslice_audio_map),
+ .fully_routed = true,
+};
+
+static const struct tegra_asoc_data tegra_trimslice_data = {
+ .mclk_rate = tegra_machine_mclk_rate_128,
+ .card = &snd_soc_tegra_trimslice,
+ .add_common_snd_ops = true,
+};
+
+/* RT5677 machine */
+
+static int tegra_rt5677_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ int err;
+
+ err = tegra_asoc_machine_init(rtd);
+ if (err)
+ return err;
+
+ snd_soc_dapm_force_enable_pin(&card->dapm, "MICBIAS1");
+
+ return 0;
+}
+
+SND_SOC_DAILINK_DEFS(rt5677_aif1,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5677-aif1")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tegra_rt5677_dai = {
+ .name = "RT5677",
+ .stream_name = "RT5677 PCM",
+ .init = tegra_rt5677_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(rt5677_aif1),
+};
+
+static struct snd_soc_card snd_soc_tegra_rt5677 = {
+ .components = "codec:rt5677",
+ .dai_link = &tegra_rt5677_dai,
+ .num_links = 1,
+ .fully_routed = true,
+};
+
+static const struct tegra_asoc_data tegra_rt5677_data = {
+ .mclk_rate = tegra_machine_mclk_rate_256,
+ .card = &snd_soc_tegra_rt5677,
+ .add_common_dapm_widgets = true,
+ .add_common_controls = true,
+ .add_common_snd_ops = true,
+ .add_mic_jack = true,
+ .add_hp_jack = true,
+};
+
+/* RT5640 machine */
+
+SND_SOC_DAILINK_DEFS(rt5640_aif1,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5640-aif1")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tegra_rt5640_dai = {
+ .name = "RT5640",
+ .stream_name = "RT5640 PCM",
+ .init = tegra_asoc_machine_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(rt5640_aif1),
+};
+
+static struct snd_soc_card snd_soc_tegra_rt5640 = {
+ .components = "codec:rt5640",
+ .dai_link = &tegra_rt5640_dai,
+ .num_links = 1,
+ .fully_routed = true,
+};
+
+static const struct tegra_asoc_data tegra_rt5640_data = {
+ .mclk_rate = tegra_machine_mclk_rate_256,
+ .card = &snd_soc_tegra_rt5640,
+ .add_common_dapm_widgets = true,
+ .add_common_controls = true,
+ .add_common_snd_ops = true,
+ .add_hp_jack = true,
+};
+
+/* RT5632 machine */
+
+SND_SOC_DAILINK_DEFS(rt5632_hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "alc5632-hifi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tegra_rt5632_dai = {
+ .name = "ALC5632",
+ .stream_name = "ALC5632 PCM",
+ .init = tegra_rt5677_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(rt5632_hifi),
+};
+
+static struct snd_soc_card snd_soc_tegra_rt5632 = {
+ .components = "codec:rt5632",
+ .dai_link = &tegra_rt5632_dai,
+ .num_links = 1,
+ .fully_routed = true,
+};
+
+static const struct tegra_asoc_data tegra_rt5632_data = {
+ .mclk_rate = tegra_machine_mclk_rate_512,
+ .card = &snd_soc_tegra_rt5632,
+ .add_common_dapm_widgets = true,
+ .add_common_controls = true,
+ .add_common_snd_ops = true,
+ .add_headset_jack = true,
+};
+
+static const struct of_device_id tegra_machine_of_match[] = {
+ { .compatible = "nvidia,tegra-audio-trimslice", .data = &tegra_trimslice_data },
+ { .compatible = "nvidia,tegra-audio-max98090", .data = &tegra_max98090_data },
+ { .compatible = "nvidia,tegra-audio-sgtl5000", .data = &tegra_sgtl5000_data },
+ { .compatible = "nvidia,tegra-audio-wm9712", .data = &tegra_wm9712_data },
+ { .compatible = "nvidia,tegra-audio-wm8753", .data = &tegra_wm8753_data },
+ { .compatible = "nvidia,tegra-audio-rt5677", .data = &tegra_rt5677_data },
+ { .compatible = "nvidia,tegra-audio-rt5640", .data = &tegra_rt5640_data },
+ { .compatible = "nvidia,tegra-audio-alc5632", .data = &tegra_rt5632_data },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra_machine_of_match);
+
+static struct platform_driver tegra_asoc_machine_driver = {
+ .driver = {
+ .name = "tegra-audio",
+ .of_match_table = tegra_machine_of_match,
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = tegra_asoc_machine_probe,
+};
+module_platform_driver(tegra_asoc_machine_driver);
+
+MODULE_AUTHOR("Anatol Pomozov <anatol@google.com>");
+MODULE_AUTHOR("Andrey Danin <danindrey@mail.ru>");
+MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");
+MODULE_AUTHOR("Ion Agorria <ion@agorria.com>");
+MODULE_AUTHOR("Leon Romanovsky <leon@leon.nu>");
+MODULE_AUTHOR("Lucas Stach <dev@lynxeye.de>");
+MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>");
+MODULE_AUTHOR("Marcel Ziswiler <marcel@ziswiler.com>");
+MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>");
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("Tegra machine ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/tegra/tegra_asoc_machine.h b/sound/soc/tegra/tegra_asoc_machine.h
new file mode 100644
index 000000000..6f795d7df
--- /dev/null
+++ b/sound/soc/tegra/tegra_asoc_machine.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __TEGRA_ASOC_MACHINE_H__
+#define __TEGRA_ASOC_MACHINE_H__
+
+struct clk;
+struct gpio_desc;
+struct snd_soc_card;
+struct snd_soc_jack;
+struct platform_device;
+struct snd_soc_jack_gpio;
+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;
+ bool add_common_dapm_widgets;
+ bool add_common_controls;
+ bool add_common_snd_ops;
+ bool add_headset_jack;
+ bool add_mic_jack;
+ bool add_hp_jack;
+ bool set_ac97;
+};
+
+struct tegra_machine {
+ struct clk *clk_pll_a_out0;
+ struct clk *clk_pll_a;
+ struct clk *clk_cdev1;
+ unsigned int set_baseclock;
+ unsigned int set_mclk;
+ const struct tegra_asoc_data *asoc;
+ struct gpio_desc *gpiod_ext_mic_en;
+ struct gpio_desc *gpiod_int_mic_en;
+ struct gpio_desc *gpiod_spkr_en;
+ struct gpio_desc *gpiod_mic_det;
+ struct gpio_desc *gpiod_ear_sel;
+ struct gpio_desc *gpiod_hp_mute;
+ struct gpio_desc *gpiod_hp_det;
+ struct snd_soc_jack *mic_jack;
+ struct snd_soc_jack_gpio *hp_jack_gpio;
+};
+
+int tegra_asoc_machine_probe(struct platform_device *pdev);
+int tegra_asoc_machine_init(struct snd_soc_pcm_runtime *rtd);
+
+#endif
diff --git a/sound/soc/tegra/tegra_audio_graph_card.c b/sound/soc/tegra/tegra_audio_graph_card.c
new file mode 100644
index 000000000..4737e776d
--- /dev/null
+++ b/sound/soc/tegra/tegra_audio_graph_card.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// tegra_audio_graph_card.c - Audio Graph based Tegra Machine Driver
+//
+// Copyright (c) 2020-2021 NVIDIA CORPORATION. All rights reserved.
+
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <sound/graph_card.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dai.h>
+
+#define MAX_PLLA_OUT0_DIV 128
+
+#define simple_to_tegra_priv(simple) \
+ container_of(simple, struct tegra_audio_priv, simple)
+
+enum srate_type {
+ /*
+ * Sample rates multiple of 8000 Hz and below are supported:
+ * ( 8000, 16000, 32000, 48000, 96000, 192000 Hz )
+ */
+ x8_RATE,
+
+ /*
+ * Sample rates multiple of 11025 Hz and below are supported:
+ * ( 11025, 22050, 44100, 88200, 176400 Hz )
+ */
+ x11_RATE,
+
+ NUM_RATE_TYPE,
+};
+
+struct tegra_audio_priv {
+ struct asoc_simple_priv simple;
+ struct clk *clk_plla_out0;
+ struct clk *clk_plla;
+};
+
+/* Tegra audio chip data */
+struct tegra_audio_cdata {
+ unsigned int plla_rates[NUM_RATE_TYPE];
+ unsigned int plla_out0_rates[NUM_RATE_TYPE];
+};
+
+static bool need_clk_update(struct snd_soc_dai *dai)
+{
+ if (snd_soc_dai_is_dummy(dai) ||
+ !dai->driver->ops ||
+ !dai->driver->name)
+ return false;
+
+ if (strstr(dai->driver->name, "I2S") ||
+ strstr(dai->driver->name, "DMIC") ||
+ strstr(dai->driver->name, "DSPK"))
+ return true;
+
+ return false;
+}
+
+/* Setup PLL clock as per the given sample rate */
+static int tegra_audio_graph_update_pll(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct asoc_simple_priv *simple = snd_soc_card_get_drvdata(rtd->card);
+ struct tegra_audio_priv *priv = simple_to_tegra_priv(simple);
+ struct device *dev = rtd->card->dev;
+ const struct tegra_audio_cdata *data = of_device_get_match_data(dev);
+ unsigned int plla_rate, plla_out0_rate, bclk;
+ unsigned int srate = params_rate(params);
+ int err;
+
+ switch (srate) {
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ case 176400:
+ plla_out0_rate = data->plla_out0_rates[x11_RATE];
+ plla_rate = data->plla_rates[x11_RATE];
+ break;
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
+ case 96000:
+ case 192000:
+ plla_out0_rate = data->plla_out0_rates[x8_RATE];
+ plla_rate = data->plla_rates[x8_RATE];
+ break;
+ default:
+ dev_err(rtd->card->dev, "Unsupported sample rate %u\n",
+ srate);
+ return -EINVAL;
+ }
+
+ /*
+ * Below is the clock relation:
+ *
+ * PLLA
+ * |
+ * |--> PLLA_OUT0
+ * |
+ * |---> I2S modules
+ * |
+ * |---> DMIC modules
+ * |
+ * |---> DSPK modules
+ *
+ *
+ * Default PLLA_OUT0 rate might be too high when I/O is running
+ * at minimum PCM configurations. This may result in incorrect
+ * clock rates and glitchy audio. The maximum divider is 128
+ * and any thing higher than that won't work. Thus reduce PLLA_OUT0
+ * to work for lower configurations.
+ *
+ * This problem is seen for I2S only, as DMIC and DSPK minimum
+ * clock requirements are under allowed divider limits.
+ */
+ bclk = srate * params_channels(params) * params_width(params);
+ if (div_u64(plla_out0_rate, bclk) > MAX_PLLA_OUT0_DIV)
+ plla_out0_rate >>= 1;
+
+ dev_dbg(rtd->card->dev,
+ "Update clock rates: PLLA(= %u Hz) and PLLA_OUT0(= %u Hz)\n",
+ plla_rate, plla_out0_rate);
+
+ /* Set PLLA rate */
+ err = clk_set_rate(priv->clk_plla, plla_rate);
+ if (err) {
+ dev_err(rtd->card->dev,
+ "Can't set plla rate for %u, err: %d\n",
+ plla_rate, err);
+ return err;
+ }
+
+ /* Set PLLA_OUT0 rate */
+ err = clk_set_rate(priv->clk_plla_out0, plla_out0_rate);
+ if (err) {
+ dev_err(rtd->card->dev,
+ "Can't set plla_out0 rate %u, err: %d\n",
+ plla_out0_rate, err);
+ return err;
+ }
+
+ return err;
+}
+
+static int tegra_audio_graph_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ int err;
+
+ if (need_clk_update(cpu_dai)) {
+ err = tegra_audio_graph_update_pll(substream, params);
+ if (err)
+ return err;
+ }
+
+ return asoc_simple_hw_params(substream, params);
+}
+
+static const struct snd_soc_ops tegra_audio_graph_ops = {
+ .startup = asoc_simple_startup,
+ .shutdown = asoc_simple_shutdown,
+ .hw_params = tegra_audio_graph_hw_params,
+};
+
+static int tegra_audio_graph_card_probe(struct snd_soc_card *card)
+{
+ struct asoc_simple_priv *simple = snd_soc_card_get_drvdata(card);
+ struct tegra_audio_priv *priv = simple_to_tegra_priv(simple);
+
+ priv->clk_plla = devm_clk_get(card->dev, "pll_a");
+ if (IS_ERR(priv->clk_plla)) {
+ dev_err(card->dev, "Can't retrieve clk pll_a\n");
+ return PTR_ERR(priv->clk_plla);
+ }
+
+ priv->clk_plla_out0 = devm_clk_get(card->dev, "plla_out0");
+ if (IS_ERR(priv->clk_plla_out0)) {
+ dev_err(card->dev, "Can't retrieve clk plla_out0\n");
+ return PTR_ERR(priv->clk_plla_out0);
+ }
+
+ return asoc_graph_card_probe(card);
+}
+
+static int tegra_audio_graph_probe(struct platform_device *pdev)
+{
+ struct tegra_audio_priv *priv;
+ struct device *dev = &pdev->dev;
+ struct snd_soc_card *card;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ card = simple_priv_to_card(&priv->simple);
+ card->driver_name = "tegra-ape";
+
+ card->probe = tegra_audio_graph_card_probe;
+
+ /* audio_graph_parse_of() depends on below */
+ card->component_chaining = 1;
+ priv->simple.ops = &tegra_audio_graph_ops;
+ priv->simple.force_dpcm = 1;
+
+ return audio_graph_parse_of(&priv->simple, dev);
+}
+
+static const struct tegra_audio_cdata tegra210_data = {
+ /* PLLA */
+ .plla_rates[x8_RATE] = 368640000,
+ .plla_rates[x11_RATE] = 338688000,
+ /* PLLA_OUT0 */
+ .plla_out0_rates[x8_RATE] = 49152000,
+ .plla_out0_rates[x11_RATE] = 45158400,
+};
+
+static const struct tegra_audio_cdata tegra186_data = {
+ /* PLLA */
+ .plla_rates[x8_RATE] = 245760000,
+ .plla_rates[x11_RATE] = 270950400,
+ /* PLLA_OUT0 */
+ .plla_out0_rates[x8_RATE] = 49152000,
+ .plla_out0_rates[x11_RATE] = 45158400,
+};
+
+static const struct of_device_id graph_of_tegra_match[] = {
+ { .compatible = "nvidia,tegra210-audio-graph-card",
+ .data = &tegra210_data },
+ { .compatible = "nvidia,tegra186-audio-graph-card",
+ .data = &tegra186_data },
+ {},
+};
+MODULE_DEVICE_TABLE(of, graph_of_tegra_match);
+
+static struct platform_driver tegra_audio_graph_card = {
+ .driver = {
+ .name = "tegra-audio-graph-card",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = graph_of_tegra_match,
+ },
+ .probe = tegra_audio_graph_probe,
+ .remove = asoc_simple_remove,
+};
+module_platform_driver(tegra_audio_graph_card);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ASoC Tegra Audio Graph Sound Card");
+MODULE_AUTHOR("Sameer Pujar <spujar@nvidia.com>");
diff --git a/sound/soc/tegra/tegra_cif.h b/sound/soc/tegra/tegra_cif.h
new file mode 100644
index 000000000..7cca8068f
--- /dev/null
+++ b/sound/soc/tegra/tegra_cif.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra_cif.h - TEGRA Audio CIF Programming
+ *
+ * Copyright (c) 2020 NVIDIA CORPORATION. All rights reserved.
+ *
+ */
+
+#ifndef __TEGRA_CIF_H__
+#define __TEGRA_CIF_H__
+
+#include <linux/regmap.h>
+
+#define TEGRA_ACIF_CTRL_FIFO_TH_SHIFT 24
+#define TEGRA_ACIF_CTRL_AUDIO_CH_SHIFT 20
+#define TEGRA_ACIF_CTRL_CLIENT_CH_SHIFT 16
+#define TEGRA_ACIF_CTRL_AUDIO_BITS_SHIFT 12
+#define TEGRA_ACIF_CTRL_CLIENT_BITS_SHIFT 8
+#define TEGRA_ACIF_CTRL_EXPAND_SHIFT 6
+#define TEGRA_ACIF_CTRL_STEREO_CONV_SHIFT 4
+#define TEGRA_ACIF_CTRL_REPLICATE_SHIFT 3
+#define TEGRA_ACIF_CTRL_TRUNCATE_SHIFT 1
+#define TEGRA_ACIF_CTRL_MONO_CONV_SHIFT 0
+
+/* AUDIO/CLIENT_BITS values */
+#define TEGRA_ACIF_BITS_8 1
+#define TEGRA_ACIF_BITS_16 3
+#define TEGRA_ACIF_BITS_24 5
+#define TEGRA_ACIF_BITS_32 7
+
+#define TEGRA_ACIF_UPDATE_MASK 0x3ffffffb
+
+struct tegra_cif_conf {
+ unsigned int threshold;
+ unsigned int audio_ch;
+ unsigned int client_ch;
+ unsigned int audio_bits;
+ unsigned int client_bits;
+ unsigned int expand;
+ unsigned int stereo_conv;
+ unsigned int replicate;
+ unsigned int truncate;
+ unsigned int mono_conv;
+};
+
+static inline void tegra_set_cif(struct regmap *regmap, unsigned int reg,
+ struct tegra_cif_conf *conf)
+{
+ unsigned int value;
+
+ value = (conf->threshold << TEGRA_ACIF_CTRL_FIFO_TH_SHIFT) |
+ ((conf->audio_ch - 1) << TEGRA_ACIF_CTRL_AUDIO_CH_SHIFT) |
+ ((conf->client_ch - 1) << TEGRA_ACIF_CTRL_CLIENT_CH_SHIFT) |
+ (conf->audio_bits << TEGRA_ACIF_CTRL_AUDIO_BITS_SHIFT) |
+ (conf->client_bits << TEGRA_ACIF_CTRL_CLIENT_BITS_SHIFT) |
+ (conf->expand << TEGRA_ACIF_CTRL_EXPAND_SHIFT) |
+ (conf->stereo_conv << TEGRA_ACIF_CTRL_STEREO_CONV_SHIFT) |
+ (conf->replicate << TEGRA_ACIF_CTRL_REPLICATE_SHIFT) |
+ (conf->truncate << TEGRA_ACIF_CTRL_TRUNCATE_SHIFT) |
+ (conf->mono_conv << TEGRA_ACIF_CTRL_MONO_CONV_SHIFT);
+
+ regmap_update_bits(regmap, reg, TEGRA_ACIF_UPDATE_MASK, value);
+}
+
+#endif
diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c
new file mode 100644
index 000000000..468c8e77d
--- /dev/null
+++ b/sound/soc/tegra/tegra_pcm.c
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tegra_pcm.c - Tegra PCM driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010,2012 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ * Vijay Mali <vmali@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ */
+
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+#include "tegra_pcm.h"
+
+static const struct snd_pcm_hardware tegra_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED,
+ .period_bytes_min = 1024,
+ .period_bytes_max = PAGE_SIZE,
+ .periods_min = 2,
+ .periods_max = 8,
+ .buffer_bytes_max = PAGE_SIZE * 8,
+ .fifo_size = 4,
+};
+
+static const struct snd_dmaengine_pcm_config tegra_dmaengine_pcm_config = {
+ .pcm_hardware = &tegra_pcm_hardware,
+ .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+ .prealloc_buffer_size = PAGE_SIZE * 8,
+};
+
+int tegra_pcm_platform_register(struct device *dev)
+{
+ return snd_dmaengine_pcm_register(dev, &tegra_dmaengine_pcm_config, 0);
+}
+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)
+{
+ *config = tegra_dmaengine_pcm_config;
+ config->dma_dev = dev->parent;
+ config->chan_names[0] = txdmachan;
+ config->chan_names[1] = rxdmachan;
+
+ return snd_dmaengine_pcm_register(dev, config, 0);
+}
+EXPORT_SYMBOL_GPL(tegra_pcm_platform_register_with_chan_names);
+
+void tegra_pcm_platform_unregister(struct device *dev)
+{
+ return snd_dmaengine_pcm_unregister(dev);
+}
+EXPORT_SYMBOL_GPL(tegra_pcm_platform_unregister);
+
+int tegra_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_dmaengine_dai_dma_data *dmap;
+ struct dma_chan *chan;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ int ret;
+
+ if (rtd->dai_link->no_pcm)
+ return 0;
+
+ dmap = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ /* Set HW params now that initialization is complete */
+ snd_soc_set_runtime_hwparams(substream, &tegra_pcm_hardware);
+
+ /* Ensure period size is multiple of 8 */
+ ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 0x8);
+ if (ret) {
+ dev_err(rtd->dev, "failed to set constraint %d\n", ret);
+ return ret;
+ }
+
+ chan = dma_request_slave_channel(cpu_dai->dev, dmap->chan_name);
+ if (!chan) {
+ dev_err(cpu_dai->dev,
+ "dmaengine request slave channel failed! (%s)\n",
+ dmap->chan_name);
+ return -ENODEV;
+ }
+
+ ret = snd_dmaengine_pcm_open(substream, chan);
+ if (ret) {
+ dev_err(rtd->dev,
+ "dmaengine pcm open failed with err %d (%s)\n", ret,
+ dmap->chan_name);
+
+ dma_release_channel(chan);
+
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_pcm_open);
+
+int tegra_pcm_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ if (rtd->dai_link->no_pcm)
+ return 0;
+
+ snd_dmaengine_pcm_close_release_chan(substream);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_pcm_close);
+
+int tegra_pcm_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_dmaengine_dai_dma_data *dmap;
+ struct dma_slave_config slave_config;
+ struct dma_chan *chan;
+ int ret;
+
+ if (rtd->dai_link->no_pcm)
+ return 0;
+
+ dmap = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+ if (!dmap)
+ return 0;
+
+ chan = snd_dmaengine_pcm_get_chan(substream);
+
+ ret = snd_hwparams_to_dma_slave_config(substream, params,
+ &slave_config);
+ if (ret) {
+ dev_err(rtd->dev, "hw params config failed with err %d\n", ret);
+ return ret;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ slave_config.dst_addr = dmap->addr;
+ slave_config.dst_maxburst = 8;
+ } else {
+ slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ slave_config.src_addr = dmap->addr;
+ slave_config.src_maxburst = 8;
+ }
+
+ ret = dmaengine_slave_config(chan, &slave_config);
+ if (ret < 0) {
+ dev_err(rtd->dev, "dma slave config failed with err %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_pcm_hw_params);
+
+snd_pcm_uframes_t tegra_pcm_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ return snd_dmaengine_pcm_pointer(substream);
+}
+EXPORT_SYMBOL_GPL(tegra_pcm_pointer);
+
+static int tegra_pcm_dma_allocate(struct device *dev, struct snd_soc_pcm_runtime *rtd,
+ size_t size)
+{
+ struct snd_pcm *pcm = rtd->pcm;
+ int ret;
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ if (ret < 0)
+ return ret;
+
+ return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC, dev, size);
+}
+
+int tegra_pcm_construct(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ struct device *dev = component->dev;
+
+ /*
+ * Fallback for backwards-compatibility with older device trees that
+ * have the iommus property in the virtual, top-level "sound" node.
+ */
+ if (!of_get_property(dev->of_node, "iommus", NULL))
+ dev = rtd->card->snd_card->dev;
+
+ return tegra_pcm_dma_allocate(dev, rtd, tegra_pcm_hardware.buffer_bytes_max);
+}
+EXPORT_SYMBOL_GPL(tegra_pcm_construct);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra PCM ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h
new file mode 100644
index 000000000..2a36eea17
--- /dev/null
+++ b/sound/soc/tegra/tegra_pcm.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * tegra_pcm.h - Definitions for Tegra PCM driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010,2012 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ */
+
+#ifndef __TEGRA_PCM_H__
+#define __TEGRA_PCM_H__
+
+#include <sound/dmaengine_pcm.h>
+#include <sound/asound.h>
+
+int tegra_pcm_construct(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd);
+int tegra_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream);
+int tegra_pcm_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream);
+int tegra_pcm_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params);
+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);
+void tegra_pcm_platform_unregister(struct device *dev);
+
+#endif
diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c
new file mode 100644
index 000000000..b3cd0a34d
--- /dev/null
+++ b/sound/soc/tegra/tegra_wm8903.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * tegra_wm8903.c - Tegra machine ASoC driver for boards using WM8903 codec.
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010-2012 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd.
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+
+#include "../codecs/wm8903.h"
+
+#include "tegra_asoc_machine.h"
+
+static struct snd_soc_jack_pin tegra_wm8903_mic_jack_pins[] = {
+ { .pin = "Mic Jack", .mask = SND_JACK_MICROPHONE },
+};
+
+static unsigned int tegra_wm8903_mclk_rate(unsigned int srate)
+{
+ unsigned int mclk;
+
+ switch (srate) {
+ case 64000:
+ case 88200:
+ case 96000:
+ mclk = 128 * srate;
+ break;
+ default:
+ mclk = 256 * srate;
+ break;
+ }
+ /* FIXME: Codec only requires >= 3MHz if OSR==0 */
+ while (mclk < 6000000)
+ mclk *= 2;
+
+ return mclk;
+}
+
+static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_card *card = rtd->card;
+ int err;
+
+ /*
+ * Older version of machine driver was ignoring GPIO polarity,
+ * forcing it to active-low. This means that all older device-trees
+ * which set the polarity to active-high are wrong and we need to fix
+ * them up.
+ */
+ if (machine->asoc->hp_jack_gpio_active_low) {
+ bool active_low = gpiod_is_active_low(machine->gpiod_hp_det);
+
+ machine->hp_jack_gpio->invert = !active_low;
+ }
+
+ err = tegra_asoc_machine_init(rtd);
+ if (err)
+ return err;
+
+ if (!machine->gpiod_mic_det && machine->asoc->add_mic_jack) {
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ int shrt = 0;
+
+ 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;
+ }
+
+ if (of_property_read_bool(card->dev->of_node, "nvidia,headset"))
+ shrt = SND_JACK_MICROPHONE;
+
+ wm8903_mic_detect(component, machine->mic_jack,
+ SND_JACK_MICROPHONE, shrt);
+ }
+
+ snd_soc_dapm_force_enable_pin(&card->dapm, "MICBIAS");
+
+ return 0;
+}
+
+static int tegra_wm8903_remove(struct snd_soc_card *card)
+{
+ struct snd_soc_dai_link *link = &card->dai_link[0];
+ struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card, link);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+
+ wm8903_mic_detect(component, NULL, 0, 0);
+
+ return 0;
+}
+
+SND_SOC_DAILINK_DEFS(hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8903-hifi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tegra_wm8903_dai = {
+ .name = "WM8903",
+ .stream_name = "WM8903 PCM",
+ .init = tegra_wm8903_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(hifi),
+};
+
+static struct snd_soc_card snd_soc_tegra_wm8903 = {
+ .components = "codec:wm8903",
+ .owner = THIS_MODULE,
+ .dai_link = &tegra_wm8903_dai,
+ .num_links = 1,
+ .remove = tegra_wm8903_remove,
+ .fully_routed = true,
+};
+
+/* older device-trees used wrong polarity for the headphones-detection GPIO */
+static const struct tegra_asoc_data tegra_wm8903_data_legacy = {
+ .mclk_rate = tegra_wm8903_mclk_rate,
+ .card = &snd_soc_tegra_wm8903,
+ .hp_jack_gpio_active_low = true,
+ .add_common_dapm_widgets = true,
+ .add_common_controls = true,
+ .add_common_snd_ops = true,
+ .add_mic_jack = true,
+ .add_hp_jack = true,
+};
+
+static const struct tegra_asoc_data tegra_wm8903_data = {
+ .mclk_rate = tegra_wm8903_mclk_rate,
+ .card = &snd_soc_tegra_wm8903,
+ .add_common_dapm_widgets = true,
+ .add_common_controls = true,
+ .add_common_snd_ops = true,
+ .add_mic_jack = true,
+ .add_hp_jack = true,
+};
+
+static const struct of_device_id tegra_wm8903_of_match[] = {
+ { .compatible = "ad,tegra-audio-plutux", .data = &tegra_wm8903_data_legacy },
+ { .compatible = "ad,tegra-audio-wm8903-medcom-wide", .data = &tegra_wm8903_data_legacy },
+ { .compatible = "ad,tegra-audio-wm8903-tec", .data = &tegra_wm8903_data_legacy },
+ { .compatible = "nvidia,tegra-audio-wm8903-cardhu", .data = &tegra_wm8903_data_legacy },
+ { .compatible = "nvidia,tegra-audio-wm8903-harmony", .data = &tegra_wm8903_data_legacy },
+ { .compatible = "nvidia,tegra-audio-wm8903-picasso", .data = &tegra_wm8903_data_legacy },
+ { .compatible = "nvidia,tegra-audio-wm8903-seaboard", .data = &tegra_wm8903_data_legacy },
+ { .compatible = "nvidia,tegra-audio-wm8903-ventana", .data = &tegra_wm8903_data_legacy },
+ { .compatible = "nvidia,tegra-audio-wm8903", .data = &tegra_wm8903_data },
+ {},
+};
+MODULE_DEVICE_TABLE(of, tegra_wm8903_of_match);
+
+static struct platform_driver tegra_wm8903_driver = {
+ .driver = {
+ .name = "tegra-wm8903",
+ .of_match_table = tegra_wm8903_of_match,
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = tegra_asoc_machine_probe,
+};
+module_platform_driver(tegra_wm8903_driver);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra+WM8903 machine ASoC driver");
+MODULE_LICENSE("GPL");