diff --git a/sound/soc/tegra-alt/Kconfig b/sound/soc/tegra-alt/Kconfig
index c4b34c91..db08a54d 100644
--- a/sound/soc/tegra-alt/Kconfig
+++ b/sound/soc/tegra-alt/Kconfig
@@ -4,6 +4,12 @@ config SND_SOC_TEGRA_ALT_186
depends on ARCH_TEGRA_APE
depends on ARCH_TEGRA_18x_SOC
+config SND_SOC_TEGRA186_DSPK_ALT
+ tristate "Tegra186 DSPK driver"
+ depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_186
+ help
+ Say Y or M if you want to add support for Tegra186 DSPK module.
+
config SND_SOC_TEGRA186_ASRC_ALT
tristate "Tegra186 ASRC driver"
depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_186
@@ -36,6 +42,7 @@ config SND_SOC_TEGRA_T186REF_ALT
select SND_SOC_TEGRA210_SPDIF_ALT
select SND_SOC_TEGRA186_ASRC_ALT
select SND_SOC_TEGRA186_ARAD_ALT
+ select SND_SOC_TEGRA186_DSPK_ALT
select SND_SOC_TEGRA_ASOC_HWDEP_ALT
select SND_SOC_SPDIF
select SND_SOC_AD193X
diff --git a/sound/soc/tegra-alt/Makefile b/sound/soc/tegra-alt/Makefile
index 954f28d4..cdef1c82 100644
--- a/sound/soc/tegra-alt/Makefile
+++ b/sound/soc/tegra-alt/Makefile
@@ -4,11 +4,15 @@ snd-soc-tegra186-alt-asrc-objs := tegra186_asrc_alt.o
snd-soc-tegra186-alt-arad-objs := tegra186_arad_alt.o
snd-soc-tegra186-alt-afc-objs := tegra186_afc_alt.o
snd-soc-tegra186-alt-xbar-objs := tegra186_xbar_alt.o
+snd-soc-tegra186-alt-fpga-clock-objs := ahub_unit_fpga_clock_t18x.o
+snd-soc-tegra186-alt-dspk-objs := tegra186_dspk_alt.o
obj-$(CONFIG_SND_SOC_TEGRA186_ASRC_ALT) += snd-soc-tegra186-alt-asrc.o
obj-$(CONFIG_SND_SOC_TEGRA186_ARAD_ALT) += snd-soc-tegra186-alt-arad.o
obj-$(CONFIG_SND_SOC_TEGRA210_AFC_ALT) += snd-soc-tegra186-alt-afc.o
obj-$(CONFIG_SND_SOC_TEGRA210_XBAR_ALT) += snd-soc-tegra186-alt-xbar.o
+obj-$(CONFIG_SND_SOC_TEGRA_ALT) += snd-soc-tegra186-alt-fpga-clock.o
+obj-$(CONFIG_SND_SOC_TEGRA186_DSPK_ALT) += snd-soc-tegra186-alt-dspk.o
# The funky manipulation of $(srctree) is for dynamically finding the name of
# the kernel root dir.
diff --git a/sound/soc/tegra-alt/tegra186_dspk_alt.c b/sound/soc/tegra-alt/tegra186_dspk_alt.c
new file mode 100644
index 00000000..c478134e
--- /dev/null
+++ b/sound/soc/tegra-alt/tegra186_dspk_alt.c
@@ -0,0 +1,473 @@
+/*
+ * tegra186_dspk_alt.c - Tegra186 DSPK driver
+ *
+ * Copyright (c) 2015 NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "tegra210_xbar_alt.h"
+#include "tegra186_xbar_alt.h"
+#include "tegra186_dspk_alt.h"
+#include "ahub_unit_fpga_clock.h"
+#include "ahub_unit_fpga_clock_t18x.h"
+
+#define DRV_NAME "tegra186-dspk"
+
+static const struct reg_default tegra186_dspk_reg_defaults[] = {
+ { TEGRA186_DSPK_AXBAR_RX_INT_MASK, 0x00000007},
+ { TEGRA186_DSPK_AXBAR_RX_CIF_CTRL, 0x00007700},
+ { TEGRA186_DSPK_CG, 0x00000001},
+ { TEGRA186_DSPK_CORE_CTRL, 0x00000310},
+ { TEGRA186_DSPK_CODEC_CTRL, 0x03000000},
+ { TEGRA186_DSPK_SDM_COEF_A_2, 0x000013bb},
+ { TEGRA186_DSPK_SDM_COEF_A_3, 0x00001cbf},
+ { TEGRA186_DSPK_SDM_COEF_A_4, 0x000029d7},
+ { TEGRA186_DSPK_SDM_COEF_A_5, 0x00003782},
+ { TEGRA186_DSPK_SDM_COEF_C_1, 0x000000a6},
+ { TEGRA186_DSPK_SDM_COEF_C_2, 0x00001959},
+ { TEGRA186_DSPK_SDM_COEF_C_3, 0x00002b9f},
+ { TEGRA186_DSPK_SDM_COEF_C_4, 0x00004218},
+ { TEGRA186_DSPK_SDM_COEF_G_1, 0x00000074},
+ { TEGRA186_DSPK_SDM_COEF_G_2, 0x0000007d},
+};
+
+static int tegra186_dspk_runtime_suspend(struct device *dev)
+{
+ struct tegra186_dspk *dspk = dev_get_drvdata(dev);
+ regcache_cache_only(dspk->regmap, true);
+ pm_runtime_put_sync(dev->parent);
+ return 0;
+}
+
+static int tegra186_dspk_runtime_resume(struct device *dev)
+{
+ struct tegra186_dspk *dspk = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pm_runtime_get_sync(dev->parent);
+ if (ret < 0) {
+ dev_err(dev, "parent get_sync failed: %d\n", ret);
+ return ret;
+ }
+ regcache_cache_only(dspk->regmap, false);
+ regcache_sync(dspk->regmap);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tegra186_dspk_suspend(struct device *dev)
+{
+ struct tegra186_dspk *dspk = dev_get_drvdata(dev);
+ regcache_mark_dirty(dspk->regmap);
+ return 0;
+}
+#endif
+
+static int tegra186_dspk_set_audio_cif(struct tegra186_dspk *dspk,
+ struct snd_pcm_hw_params *params,
+ unsigned int reg, struct snd_soc_dai *dai)
+{
+ int channels;
+ struct tegra210_xbar_cif_conf cif_conf;
+ struct device *dev = dai->dev;
+
+ channels = params_channels(params);
+ memset(&cif_conf, 0, sizeof(struct tegra210_xbar_cif_conf));
+ cif_conf.audio_channels = channels;
+ cif_conf.client_channels = channels;
+ cif_conf.client_bits = TEGRA210_AUDIOCIF_BITS_24;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ cif_conf.audio_bits = TEGRA210_AUDIOCIF_BITS_16;
+ cif_conf.client_bits = TEGRA210_AUDIOCIF_BITS_16;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ cif_conf.audio_bits = TEGRA210_AUDIOCIF_BITS_32;
+ break;
+ default:
+ dev_err(dev, "Wrong format!\n");
+ return -EINVAL;
+ }
+
+ dspk->soc_data->set_audio_cif(dspk->regmap,
+ TEGRA186_DSPK_AXBAR_RX_CIF_CTRL,
+ &cif_conf);
+ 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 device *dev = dai->dev;
+ struct tegra186_dspk *dspk = snd_soc_dai_get_drvdata(dai);
+ int channels, srate, ret, dspk_clk;
+ int osr = TEGRA186_DSPK_OSR_64;
+ int interface_clk_ratio = 4; /* dspk interface clock should be fsout*4 */
+
+ channels = params_channels(params);
+ srate = params_rate(params);
+ dspk_clk = (1 << (5+osr)) * srate * interface_clk_ratio;
+
+ if ((tegra_platform_is_unit_fpga() || tegra_platform_is_fpga())) {
+ program_dspk_clk(dspk_clk);
+ } else {
+ ret = clk_set_rate(dspk->clk_dspk, dspk_clk);
+ if (ret) {
+ dev_err(dev, "Can't set dspk clock rate: %d\n", ret);
+ return ret;
+ }
+ }
+
+ regmap_update_bits(dspk->regmap,
+ TEGRA186_DSPK_CORE_CTRL,
+ TEGRA186_DSPK_OSR_MASK,
+ osr << TEGRA186_DSPK_OSR_SHIFT);
+
+ regmap_update_bits(dspk->regmap,
+ TEGRA186_DSPK_CORE_CTRL,
+ TEGRA186_DSPK_CHANNEL_SELECT_MASK,
+ ((1 << channels) - 1) <<
+ TEGRA186_DSPK_CHANNEL_SELECT_SHIFT);
+
+ /* program cif control register */
+ ret = tegra186_dspk_set_audio_cif(dspk, params,
+ TEGRA186_DSPK_AXBAR_RX_CIF_CTRL,
+ dai);
+
+ if (ret)
+ dev_err(dev, "Can't set dspk RX CIF: %d\n", ret);
+ return ret;
+}
+
+static int tegra186_dspk_codec_probe(struct snd_soc_codec *codec)
+{
+ struct tegra186_dspk *dspk = snd_soc_codec_get_drvdata(codec);
+
+ codec->control_data = dspk->regmap;
+
+ return 0;
+}
+
+static 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 = "DAP",
+ .capture = {
+ .stream_name = "DSPK Transmit",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra186_dspk_dai_ops,
+ .symmetric_rates = 1,
+ },
+ {
+ .name = "CIF",
+ .playback = {
+ .stream_name = "DSPK Receive",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra186_dspk_dai_ops,
+ .symmetric_rates = 1,
+ }
+};
+
+static const struct snd_soc_dapm_widget tegra186_dspk_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("DSPK RX", NULL, 0, TEGRA186_DSPK_ENABLE, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("DSPK TX", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route tegra186_dspk_routes[] = {
+ { "DSPK RX", NULL, "DSPK Receive" },
+ { "DSPK TX", NULL, "DSPK RX" },
+ { "DSPK Transmit", NULL, "DSPK TX" },
+};
+
+static struct snd_soc_codec_driver tegra186_dspk_codec = {
+ .probe = tegra186_dspk_codec_probe,
+ .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),
+ .idle_bias_off = 1,
+};
+
+/* Regmap callback functions */
+static bool tegra186_dspk_wr_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA186_DSPK_AXBAR_RX_INT_MASK:
+ case TEGRA186_DSPK_AXBAR_RX_INT_SET:
+ case TEGRA186_DSPK_AXBAR_RX_INT_CLEAR:
+ case TEGRA186_DSPK_AXBAR_RX_CIF_CTRL:
+ case TEGRA186_DSPK_AXBAR_RX_CYA:
+ case TEGRA186_DSPK_ENABLE:
+ case TEGRA186_DSPK_SOFT_RESET:
+ case TEGRA186_DSPK_CG:
+ return true;
+ default:
+ if (((reg % 4) == 0) && (reg >= TEGRA186_DSPK_CORE_CTRL) &&
+ (reg <= TEGRA186_DSPK_SDM_COEF_G_2))
+ return true;
+ else
+ 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_AXBAR_RX_STATUS:
+ case TEGRA186_DSPK_AXBAR_RX_INT_STATUS:
+ case TEGRA186_DSPK_AXBAR_RX_CIF_FIFO_STATUS:
+ case TEGRA186_DSPK_STATUS:
+ case TEGRA186_DSPK_INT_STATUS:
+ return true;
+ default:
+ if (((reg % 4) == 0) && (reg >= TEGRA186_DSPK_DEBUG_STATUS) &&
+ (reg <= TEGRA186_DSPK_DEBUG_STAGE4_CNTR))
+ return true;
+ else
+ return false;
+ };
+}
+
+static bool tegra186_dspk_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TEGRA186_DSPK_AXBAR_RX_STATUS:
+ case TEGRA186_DSPK_AXBAR_RX_INT_STATUS:
+ case TEGRA186_DSPK_AXBAR_RX_CIF_FIFO_STATUS:
+ case TEGRA186_DSPK_STATUS:
+ case TEGRA186_DSPK_INT_STATUS:
+ return true;
+ default:
+ return false;
+ };
+}
+
+static const struct regmap_config tegra186_dspk_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = TEGRA186_DSPK_DEBUG_STAGE4_CNTR,
+ .writeable_reg = tegra186_dspk_wr_reg,
+ .readable_reg = tegra186_dspk_rd_reg,
+ .volatile_reg = tegra186_dspk_volatile_reg,
+ .precious_reg = NULL,
+ .reg_defaults = tegra186_dspk_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tegra186_dspk_reg_defaults),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct tegra186_dspk_soc_data soc_data_tegra186 = {
+ .set_audio_cif = tegra210_xbar_set_cif,
+};
+
+static const struct of_device_id tegra186_dspk_of_match[] = {
+ { .compatible = "nvidia,tegra186-dspk", .data = &soc_data_tegra186 },
+ {},
+};
+
+static int tegra186_dspk_platform_probe(struct platform_device *pdev)
+{
+ struct tegra186_dspk *dspk;
+ struct resource *mem, *memregion;
+ void __iomem *regs;
+ int ret = 0;
+ const struct of_device_id *match;
+ struct tegra186_dspk_soc_data *soc_data;
+
+ match = of_match_device(tegra186_dspk_of_match, &pdev->dev);
+ if (!match) {
+ dev_err(&pdev->dev, "Error: No device match found\n");
+ ret = -ENODEV;
+ goto err;
+ }
+ soc_data = (struct tegra186_dspk_soc_data *)match->data;
+
+ dspk = devm_kzalloc(&pdev->dev, sizeof(struct tegra186_dspk),
+ GFP_KERNEL);
+ if (!dspk) {
+ dev_err(&pdev->dev, "Can't allocate dspk\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+ dev_set_drvdata(&pdev->dev, dspk);
+
+ dspk->soc_data = soc_data;
+
+ if (!(tegra_platform_is_unit_fpga() || tegra_platform_is_fpga())) {
+ dspk->clk_dspk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dspk->clk_dspk)) {
+ dev_err(&pdev->dev, "Can't retrieve dspk clock\n");
+ ret = PTR_ERR(dspk->clk_dspk);
+ goto err;
+ }
+
+ dspk->clk_pll_a_out0 = clk_get_sys(NULL, "pll_a_out0");
+ if (IS_ERR_OR_NULL(dspk->clk_pll_a_out0)) {
+ dev_err(&pdev->dev, "Can't retrieve pll_a_out0 clock\n");
+ ret = -ENOENT;
+ goto err_clk_put;
+ }
+
+ ret = clk_set_parent(dspk->clk_dspk, dspk->clk_pll_a_out0);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't set parent of dspk clock\n");
+ goto err_plla_clk_put;
+ }
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "No memory resource\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ memregion = devm_request_mem_region(&pdev->dev, mem->start,
+ resource_size(mem), pdev->name);
+ if (!memregion) {
+ dev_err(&pdev->dev, "Memory region already claimed\n");
+ ret = -EBUSY;
+ goto err;
+ }
+
+ regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem));
+ if (!regs) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ dspk->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
+ &tegra186_dspk_regmap_config);
+ if (IS_ERR(dspk->regmap)) {
+ dev_err(&pdev->dev, "regmap init failed\n");
+ ret = PTR_ERR(dspk->regmap);
+ goto err;
+ }
+ regcache_cache_only(dspk->regmap, true);
+
+ if (of_property_read_u32(pdev->dev.of_node,
+ "nvidia,ahub-dspk-id",
+ &pdev->dev.id) < 0) {
+ dev_err(&pdev->dev,
+ "Missing property nvidia,ahub-dspk-id\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ pm_runtime_enable(&pdev->dev);
+ if (!pm_runtime_enabled(&pdev->dev)) {
+ ret = tegra186_dspk_runtime_resume(&pdev->dev);
+ if (ret)
+ goto err_pm_disable;
+ }
+
+ ret = snd_soc_register_codec(&pdev->dev, &tegra186_dspk_codec,
+ tegra186_dspk_dais,
+ ARRAY_SIZE(tegra186_dspk_dais));
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Could not register CODEC: %d\n", ret);
+ goto err_suspend;
+ }
+
+ return 0;
+
+err_suspend:
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ tegra186_dspk_runtime_suspend(&pdev->dev);
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+err_plla_clk_put:
+ devm_clk_put(&pdev->dev, dspk->clk_pll_a_out0);
+err_clk_put:
+ devm_clk_put(&pdev->dev, dspk->clk_dspk);
+err:
+ return ret;
+}
+
+
+
+static int tegra186_dspk_platform_remove(struct platform_device *pdev)
+{
+ struct tegra186_dspk *dspk;
+
+ dspk = dev_get_drvdata(&pdev->dev);
+ snd_soc_unregister_codec(&pdev->dev);
+
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ tegra186_dspk_runtime_suspend(&pdev->dev);
+
+ devm_clk_put(&pdev->dev, dspk->clk_pll_a_out0);
+ devm_clk_put(&pdev->dev, dspk->clk_dspk);
+
+ 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(tegra186_dspk_suspend, NULL)
+};
+
+static struct platform_driver tegra186_dspk_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .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 ");
+MODULE_DESCRIPTION("Tegra186 DSPK ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_DEVICE_TABLE(of, tegra186_dspk_of_match);