summaryrefslogtreecommitdiff
path: root/sound/soc/tegra/tegra210_i2s.c
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2020-07-20 15:34:33 +0100
committerMark Brown <broonie@kernel.org>2020-07-20 15:34:33 +0100
commit6b174a49e4e029c289cab8134b03b0bb76ff16c7 (patch)
tree5f51bbf4a9e45705796c334b20ce0853acdb5dff /sound/soc/tegra/tegra210_i2s.c
parent4d9e07cc41b837146ef39d99c8dad2a58326ae4a (diff)
parent327ef64702668bb754eeea80ce402454d7a1302a (diff)
Merge series "Add ASoC AHUB components for Tegra210 and later" from Sameer Pujar <spujar@nvidia.com>:
Overview ======== Audio Processing Engine (APE) comprises of Audio DMA (ADMA) and Audio Hub (AHUB) unit. AHUB is a collection of hardware accelerators for audio pre-processing and post-processing. It also includes a programmable full crossbar for routing audio data across these accelerators. This series exposes some of these below mentioned HW devices as ASoC components for Tegra platforms from Tegra210 onwards. * ADMAIF : The interface between ADMA and AHUB * XBAR : Crossbar for routing audio samples across various modules * I2S : Inter-IC Sound Controller * DMIC : Digital Microphone * DSPK : Digital Speaker Following is the summary of current series. * Add YAML DT binding documentation for above mentioned modules. * Helper function for ACIF programming is exposed for Tegra210 and later. * Add ASoC driver components for each of the above modules. * Build ACONNECT and ADMA drivers which are essential to realize audio use case. * Add DT entries for above components for Tegra210, Tegra186 and Tegra194. As per the suggestion in [0] audio graph based sound card support is pushed in a separate series. [0] https://lkml.org/lkml/2020/6/27/4 Changelog ========= v4 -> v5 -------- * Common changes - simple-card driver changes are dropped. Changes are migrated to audio graph card and are moved to a separate series as suggested. - '#sound-dai-cells' property is not needed for planned audio graph card Hence dropped from documentation and related DT binding of component drivers. - CIF and DAP DAIs are added for I/O drivers (DMIC, DSPK, I2S) to represent DAI links using audio graph card. Similary DAIs are added in AHUB driver to describe endpoints in audio crossbar. Routing is updated to reflect the same in drivers. v3 -> v4 -------- * [1/23] "ASoC: dt-bindings: tegra: Add DT bindings for Tegra210" - Removed multiple examples and retained one example per doc - Fixed as per inputs on the previous series - Tested bindings with 'make dt_binding_check/dtbs_check' * [2/23] "ASoC: tegra: Add support for CIF programming" - No change * Common changes (for patch [3/10] to [7/10]) - Mixer control overrides, for PCM parameters (rate, channel, bits), in each driver are dropped. - Updated routing as per DPCM usage - Minor changes related to formatting * New changes (patch [8/23] to [18/23] and patch [23/23]) - Based on discussions in following threads DPCM is used for Tegra Audio. https://lkml.org/lkml/2020/2/20/91 https://lkml.org/lkml/2020/4/30/519 - The simple-card driver is used for Tegra Audio and accordingly some enhancements are made in simple-card and core drivers. - Patch [8/23] to [18/23] are related to simple-card and core changes. - Patch [23/23] adds sound card support to realize complete audio path. This is based on simple-card driver with proposed enhancements. - Re-ordered patches depending on above v2 -> v3 -------- * [1/10] "dt-bindings: sound: tegra: add DT binding for AHUB - Updated licence - Removed redundancy w.r.t items/const/enum - Added constraints wherever needed with "pattern" property * [2/10] "ASoC: tegra: add support for CIF programming" - Removed tegra_cif.c - Instead added inline helper function in tegra_cif.h * common changes (for patch [3/10] to [7/10]) - Replace LATE system calls with Normal sleep - Remove explicit RPM suspend in driver remove() call - Use devm_kzalloc() instead of devm_kcalloc() for single element - Replace 'ret' with 'err' for better reading - Consistent error printing style across drivers - Minor formating fixes * [8/10] "arm64: tegra: add AHUB components for few Tegra chips" - no change * [9/10] "arm64: tegra: enable AHUB modules for few Tegra chips" - no change * [10/10] "arm64: defconfig: enable AHUB components for Tegra210 and later" (New patch) - Enables ACONNECT and AHUB components. With this AHUB and components are registered with ASoC core. v1 -> v2 -------- * [1/9] "dt-bindings: sound: tegra: add DT binding for AHUB" - no changes * [2/9] "ASoC: tegra: add support for CIF programming" - removed CIF programming changes for legacy chips. - this patch now exposes helper function for CIF programming, which can be used on Tegra210 later. - later tegra_cif.c can be extended for legacy chips as well. - updated commit message accordingly * [3/9] "ASoC: tegra: add Tegra210 based DMIC driver" - removed unnecessary initialization of 'ret' in probe() * [4/9] "ASoC: tegra: add Tegra210 based I2S driver" - removed unnecessary initialization of 'ret' in probe() - fixed indentation - added consistent bracing for if-else clauses - updated 'rx_fifo_th' type to 'unsigned int' - used BIT() macro for defines like '1 << {x}' in tegra210_i2s.h * [5/9] "ASoC: tegra: add Tegra210 based AHUB driver" - used of_device_get_match_data() to get 'soc_data' and removed explicit of_match_device() - used devm_platform_ioremap_resource() and removed explicit platform_get_resource() - fixed indentation for devm_snd_soc_register_component() - updated commit message - updated commit message to reflect compatible binding for Tegra186 and Tegra194. * [6/9] "ASoC: tegra: add Tegra186 based DSPK driver" - removed unnecessary initialization of 'ret' in probe() - updated 'max_th' to 'unsigned int' - shortened lengthy macro names to avoid wrapping in tegra186_dspk_wr_reg() and to be consistent * [7/9] "ASoC: tegra: add Tegra210 based ADMAIF driver" - used of_device_get_match_data() and removed explicit of_match_device() - used BIT() macro for defines like '1 << {x}' in tegra210_admaif.h - updated commit message to reflect compatible binding for Tegra186 and Tegra194. * [8/9] "arm64: tegra: add AHUB components for few Tegra chips" - no change * [9/9] "arm64: tegra: enable AHUB modules for few Tegra chips" - no change * common changes for patch [3/9] to [7/9] - sorted headers in alphabetical order - moved MODULE_DEVICE_TABLE() right below *_of_match table - removed macro DRV_NAME - removed explicit 'owner' field from platform_driver structure - added 'const' to snd_soc_dai_ops structure Sameer Pujar (11): ASoC: dt-bindings: tegra: Add DT bindings for Tegra210 ASoC: tegra: Add support for CIF programming ASoC: tegra: Add Tegra210 based DMIC driver ASoC: tegra: Add Tegra210 based I2S driver ASoC: tegra: Add Tegra210 based AHUB driver ASoC: tegra: Add Tegra186 based DSPK driver ASoC: tegra: Add Tegra210 based ADMAIF driver arm64: defconfig: Build AHUB component drivers arm64: defconfig: Build ADMA and ACONNECT driver arm64: tegra: Enable ACONNECT, ADMA and AGIC on Jetson Nano arm64: tegra: Add DT binding for AHUB components .../bindings/sound/nvidia,tegra186-dspk.yaml | 83 +++ .../bindings/sound/nvidia,tegra210-admaif.yaml | 111 +++ .../bindings/sound/nvidia,tegra210-ahub.yaml | 136 ++++ .../bindings/sound/nvidia,tegra210-dmic.yaml | 83 +++ .../bindings/sound/nvidia,tegra210-i2s.yaml | 101 +++ arch/arm64/boot/dts/nvidia/tegra186.dtsi | 217 +++++- arch/arm64/boot/dts/nvidia/tegra194.dtsi | 225 +++++- arch/arm64/boot/dts/nvidia/tegra210-p3450-0000.dts | 12 + arch/arm64/boot/dts/nvidia/tegra210.dtsi | 140 ++++ arch/arm64/configs/defconfig | 8 + sound/soc/tegra/Kconfig | 56 ++ sound/soc/tegra/Makefile | 10 + sound/soc/tegra/tegra186_dspk.c | 442 +++++++++++ sound/soc/tegra/tegra186_dspk.h | 70 ++ sound/soc/tegra/tegra210_admaif.c | 800 ++++++++++++++++++++ sound/soc/tegra/tegra210_admaif.h | 162 ++++ sound/soc/tegra/tegra210_ahub.c | 676 +++++++++++++++++ sound/soc/tegra/tegra210_ahub.h | 127 ++++ sound/soc/tegra/tegra210_dmic.c | 455 ++++++++++++ sound/soc/tegra/tegra210_dmic.h | 82 +++ sound/soc/tegra/tegra210_i2s.c | 812 +++++++++++++++++++++ sound/soc/tegra/tegra210_i2s.h | 126 ++++ sound/soc/tegra/tegra_cif.h | 65 ++ sound/soc/tegra/tegra_pcm.c | 235 +++++- sound/soc/tegra/tegra_pcm.h | 21 +- 25 files changed, 5251 insertions(+), 4 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra186-dspk.yaml create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra210-admaif.yaml create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra210-ahub.yaml create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra210-dmic.yaml create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra210-i2s.yaml create mode 100644 sound/soc/tegra/tegra186_dspk.c create mode 100644 sound/soc/tegra/tegra186_dspk.h create mode 100644 sound/soc/tegra/tegra210_admaif.c create mode 100644 sound/soc/tegra/tegra210_admaif.h create mode 100644 sound/soc/tegra/tegra210_ahub.c create mode 100644 sound/soc/tegra/tegra210_ahub.h create mode 100644 sound/soc/tegra/tegra210_dmic.c create mode 100644 sound/soc/tegra/tegra210_dmic.h create mode 100644 sound/soc/tegra/tegra210_i2s.c create mode 100644 sound/soc/tegra/tegra210_i2s.h create mode 100644 sound/soc/tegra/tegra_cif.h -- 2.7.4
Diffstat (limited to 'sound/soc/tegra/tegra210_i2s.c')
-rw-r--r--sound/soc/tegra/tegra210_i2s.c812
1 files changed, 812 insertions, 0 deletions
diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c
new file mode 100644
index 000000000000..722092181583
--- /dev/null
+++ b/sound/soc/tegra/tegra210_i2s.c
@@ -0,0 +1,812 @@
+// 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 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 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_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ val = 0;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ 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_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_get_control(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);
+ long *uctl_val = &ucontrol->value.integer.value[0];
+
+ if (strstr(kcontrol->id.name, "Loopback"))
+ *uctl_val = i2s->loopback;
+ else if (strstr(kcontrol->id.name, "FSYNC Width"))
+ *uctl_val = i2s->fsync_width;
+ else if (strstr(kcontrol->id.name, "Capture Stereo To Mono"))
+ *uctl_val = i2s->stereo_to_mono[I2S_TX_PATH];
+ else if (strstr(kcontrol->id.name, "Capture Mono To Stereo"))
+ *uctl_val = i2s->mono_to_stereo[I2S_TX_PATH];
+ else if (strstr(kcontrol->id.name, "Playback Stereo To Mono"))
+ *uctl_val = i2s->stereo_to_mono[I2S_RX_PATH];
+ else if (strstr(kcontrol->id.name, "Playback Mono To Stereo"))
+ *uctl_val = i2s->mono_to_stereo[I2S_RX_PATH];
+ else if (strstr(kcontrol->id.name, "Playback FIFO Threshold"))
+ *uctl_val = i2s->rx_fifo_th;
+ else if (strstr(kcontrol->id.name, "BCLK Ratio"))
+ *uctl_val = i2s->bclk_ratio;
+
+ return 0;
+}
+
+static int tegra210_i2s_put_control(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 (strstr(kcontrol->id.name, "Loopback")) {
+ i2s->loopback = value;
+
+ regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL,
+ I2S_CTRL_LPBK_MASK,
+ i2s->loopback << I2S_CTRL_LPBK_SHIFT);
+
+ } else if (strstr(kcontrol->id.name, "FSYNC Width")) {
+ /*
+ * 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.
+ */
+ i2s->fsync_width = value;
+
+ regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL,
+ I2S_CTRL_FSYNC_WIDTH_MASK,
+ i2s->fsync_width << I2S_FSYNC_WIDTH_SHIFT);
+
+ } else if (strstr(kcontrol->id.name, "Capture Stereo To Mono")) {
+ i2s->stereo_to_mono[I2S_TX_PATH] = value;
+ } else if (strstr(kcontrol->id.name, "Capture Mono To Stereo")) {
+ i2s->mono_to_stereo[I2S_TX_PATH] = value;
+ } else if (strstr(kcontrol->id.name, "Playback Stereo To Mono")) {
+ i2s->stereo_to_mono[I2S_RX_PATH] = value;
+ } else if (strstr(kcontrol->id.name, "Playback Mono To Stereo")) {
+ i2s->mono_to_stereo[I2S_RX_PATH] = value;
+ } else if (strstr(kcontrol->id.name, "Playback FIFO Threshold")) {
+ i2s->rx_fifo_th = value;
+ } else if (strstr(kcontrol->id.name, "BCLK Ratio")) {
+ i2s->bclk_ratio = value;
+ }
+
+ 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_rates = 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_control,
+ tegra210_i2s_put_control),
+ SOC_SINGLE_EXT("FSYNC Width", 0, 0, 255, 0, tegra210_i2s_get_control,
+ tegra210_i2s_put_control),
+ SOC_ENUM_EXT("Capture Stereo To Mono", tegra210_i2s_stereo_conv_enum,
+ tegra210_i2s_get_control, tegra210_i2s_put_control),
+ SOC_ENUM_EXT("Capture Mono To Stereo", tegra210_i2s_mono_conv_enum,
+ tegra210_i2s_get_control, tegra210_i2s_put_control),
+ SOC_ENUM_EXT("Playback Stereo To Mono", tegra210_i2s_stereo_conv_enum,
+ tegra210_i2s_get_control, tegra210_i2s_put_control),
+ SOC_ENUM_EXT("Playback Mono To Stereo", tegra210_i2s_mono_conv_enum,
+ tegra210_i2s_get_control, tegra210_i2s_put_control),
+ SOC_SINGLE_EXT("Playback FIFO Threshold", 0, 0, I2S_RX_FIFO_DEPTH - 1,
+ 0, tegra210_i2s_get_control, tegra210_i2s_put_control),
+ SOC_SINGLE_EXT("BCLK Ratio", 0, 0, INT_MAX, 0, tegra210_i2s_get_control,
+ tegra210_i2s_put_control),
+};
+
+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),
+ .non_legacy_dai_naming = 1,
+};
+
+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");