From 2b1b763e5cf332cd6793b1c5b704f21f09c06ff8 Mon Sep 17 00:00:00 2001 From: Mohan Kumar Date: Tue, 9 Jun 2015 21:54:03 +0800 Subject: [PATCH] ASoC: tegra-alt: T186 DSPK driver support Add DSPK driver support for T186 chip Bug 200120467 Change-Id: I805e68c6ea4c4989d392e0bfdb65fb273b47bbb8 Signed-off-by: Mohan Kumar Reviewed-on: http://git-master/r/755129 Reviewed-by: Arun Shamanna Lakshmi Tested-by: Arun Shamanna Lakshmi --- sound/soc/tegra-alt/Kconfig | 7 + sound/soc/tegra-alt/Makefile | 4 + sound/soc/tegra-alt/tegra186_dspk_alt.c | 473 ++++++++++++++++++++++++ 3 files changed, 484 insertions(+) create mode 100644 sound/soc/tegra-alt/tegra186_dspk_alt.c 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);