From 185683e8dd9505b2c14be74fc93cc6df73dc9c5c Mon Sep 17 00:00:00 2001 From: Arun Shamanna Lakshmi Date: Mon, 4 Aug 2014 12:51:34 -0700 Subject: [PATCH] ASoC: tegra-alt: Add ASoC driver for IQC module Bug 1442940 Change-Id: Ic346d9b596989306aa5612f01a76bed1e221a834 Signed-off-by: Arun Shamanna Lakshmi Reviewed-on: http://git-master/r/451055 Reviewed-by: Justin Kim (SW-TEGRA) Reviewed-by: Sumit Bhattacharya --- sound/soc/tegra-alt/Kconfig | 7 + sound/soc/tegra-alt/Makefile | 2 + sound/soc/tegra-alt/tegra210_iqc_alt.c | 458 ++++++++++++++++++++++++ sound/soc/tegra-alt/tegra210_xbar_alt.c | 2 + 4 files changed, 469 insertions(+) create mode 100644 sound/soc/tegra-alt/tegra210_iqc_alt.c diff --git a/sound/soc/tegra-alt/Kconfig b/sound/soc/tegra-alt/Kconfig index 31de2903..0fe7c48d 100644 --- a/sound/soc/tegra-alt/Kconfig +++ b/sound/soc/tegra-alt/Kconfig @@ -136,6 +136,12 @@ config SND_SOC_TEGRA210_MVC_ALT help Say Y or M if you want to add support for Tegra210 MVC module. +config SND_SOC_TEGRA210_IQC_ALT + tristate "Tegra210 IQC driver" + depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_210 + help + Say Y or M if you want to add support for Tegra210 IQC module. + config SND_SOC_TEGRA210_SPDIF_ALT tristate "Tegra210 SPDIF driver" depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_210 @@ -193,6 +199,7 @@ config SND_SOC_TEGRA_GRENADA_ALT select SND_SOC_TEGRA210_SFC_ALT select SND_SOC_TEGRA210_AFC_ALT select SND_SOC_TEGRA210_MVC_ALT + select SND_SOC_TEGRA210_IQC_ALT select SND_SOC_TEGRA210_SPDIF_ALT select SND_SOC_TEGRA210_ADSP_ALT select SND_SOC_SPDIF diff --git a/sound/soc/tegra-alt/Makefile b/sound/soc/tegra-alt/Makefile index f47a44fb..847aa5d8 100644 --- a/sound/soc/tegra-alt/Makefile +++ b/sound/soc/tegra-alt/Makefile @@ -24,6 +24,7 @@ snd-soc-tegra210-alt-mixer-objs := tegra210_mixer_alt.o snd-soc-tegra210-alt-sfc-objs := tegra210_sfc_alt.o snd-soc-tegra210-alt-afc-objs := tegra210_afc_alt.o snd-soc-tegra210-alt-mvc-objs := tegra210_mvc_alt.o +snd-soc-tegra210-alt-iqc-objs := tegra210_iqc_alt.o snd-soc-tegra210-alt-spdif-objs := tegra210_spdif_alt.o snd-soc-tegra210-alt-adsp-objs := tegra210_adsp_alt.o snd-soc-tegra210-alt-fpga-clock-objs := ahub_unit_fpga_clock.o @@ -50,6 +51,7 @@ obj-$(CONFIG_SND_SOC_TEGRA210_MIXER_ALT) += snd-soc-tegra210-alt-mixer.o obj-$(CONFIG_SND_SOC_TEGRA210_SFC_ALT) += snd-soc-tegra210-alt-sfc.o obj-$(CONFIG_SND_SOC_TEGRA210_AFC_ALT) += snd-soc-tegra210-alt-afc.o obj-$(CONFIG_SND_SOC_TEGRA210_MVC_ALT) += snd-soc-tegra210-alt-mvc.o +obj-$(CONFIG_SND_SOC_TEGRA210_IQC_ALT) += snd-soc-tegra210-alt-iqc.o obj-$(CONFIG_SND_SOC_TEGRA210_SPDIF_ALT) += snd-soc-tegra210-alt-spdif.o obj-$(CONFIG_SND_SOC_TEGRA210_ADSP_ALT) += snd-soc-tegra210-alt-adsp.o diff --git a/sound/soc/tegra-alt/tegra210_iqc_alt.c b/sound/soc/tegra-alt/tegra210_iqc_alt.c new file mode 100644 index 00000000..fd752133 --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_iqc_alt.c @@ -0,0 +1,458 @@ +/* + * tegra210_iqc.c - Tegra210 IQC driver + * + * Copyright (c) 2014 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 "tegra210_iqc_alt.h" + +#define DRV_NAME "tegra210-iqc" + +static int tegra210_iqc_runtime_suspend(struct device *dev) +{ + struct tegra210_iqc *iqc = dev_get_drvdata(dev); + + regcache_cache_only(iqc->regmap, true); + + #ifndef CONFIG_MACH_GRENADA + clk_disable_unprepare(iqc->clk_iqc); + #endif + + return 0; +} + +static int tegra210_iqc_runtime_resume(struct device *dev) +{ + struct tegra210_iqc *iqc = dev_get_drvdata(dev); + + #ifndef CONFIG_MACH_GRENADA + int ret; + + ret = clk_prepare_enable(iqc->clk_iqc); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + #endif + + regcache_cache_only(iqc->regmap, false); + + return 0; +} + +static int tegra210_iqc_set_audio_cif(struct tegra210_iqc *iqc, + struct snd_pcm_hw_params *params, + unsigned int reg) +{ + int channels, audio_bits; + struct tegra210_xbar_cif_conf cif_conf; + + channels = params_channels(params); + if (channels < 2) + return -EINVAL; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + audio_bits = TEGRA210_AUDIOCIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio_bits = TEGRA210_AUDIOCIF_BITS_32; + break; + default: + return -EINVAL; + } + + cif_conf.threshold = 0; + cif_conf.audio_channels = channels; + cif_conf.client_channels = channels; + cif_conf.audio_bits = audio_bits; + cif_conf.client_bits = audio_bits; + cif_conf.expand = 0; + cif_conf.stereo_conv = 0; + cif_conf.replicate = 0; + cif_conf.truncate = 0; + cif_conf.mono_conv = 0; + + iqc->soc_data->set_audio_cif(iqc->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra210_iqc_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_iqc *iqc = snd_soc_dai_get_drvdata(dai); + int ret; + + /* set IQC tx cif */ + ret = tegra210_iqc_set_audio_cif(iqc, params, + TEGRA210_IQC_AXBAR_TX_CIF_CTRL + + (dai->id * TEGRA210_IQC_AXBAR_TX_STRIDE)); + if (ret) { + dev_err(dev, "Can't set IQC TX CIF: %d\n", ret); + return ret; + } + + /* disable timestamp */ + if (!iqc->timestamp_enable) + regmap_update_bits(iqc->regmap, TEGRA210_IQC_CTRL, + TEGRA210_IQC_TIMESTAMP_MASK, + ~(TEGRA210_IQC_TIMESTAMP_EN)); + + /* set the IQC data offset */ + if (iqc->data_offset) + regmap_update_bits(iqc->regmap, TEGRA210_IQC_CTRL, + TEGRA210_IQC_DATA_OFFSET_MASK, + iqc->data_offset); + + return ret; +} + + +static int tegra210_iqc_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra210_iqc *iqc = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = iqc->regmap; + ret = snd_soc_codec_set_cache_io(codec, 64, 32, SND_SOC_REGMAP); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + return 0; +} + +static struct snd_soc_dai_ops tegra210_iqc_dai_ops = { + .hw_params = tegra210_iqc_hw_params, +}; + +#define IN_DAI(id) \ + { \ + .name = "DAP", \ + .playback = { \ + .stream_name = "DAP" #id " Receive", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + } + +#define OUT_DAI(id) \ + { \ + .name = "CIF", \ + .capture = { \ + .stream_name = "CIF" #id " Transmit", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .ops = &tegra210_iqc_dai_ops, \ + } + +static struct snd_soc_dai_driver tegra210_iqc_dais[] = { + OUT_DAI(1), + OUT_DAI(2), + IN_DAI(1), + IN_DAI(2), +}; + +static const struct snd_kcontrol_new tegra210_iqc_controls[] = { + SOC_SINGLE("IQC Enable", TEGRA210_IQC_ENABLE, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget tegra210_iqc_widgets[] = { + SND_SOC_DAPM_AIF_IN("IQC RX1", NULL, 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_IN("IQC RX2", NULL, 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_OUT("IQC TX1", NULL, 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_OUT("IQC TX2", NULL, 0, SND_SOC_NOPM, + 0, 0), +}; + +static const struct snd_soc_dapm_route tegra210_iqc_routes[] = { + { "IQC RX1", NULL, "DAP1 Receive" }, + { "IQC TX1", NULL, "IQC RX1" }, + { "CIF1 Transmit", NULL, "IQC TX1" }, + + { "IQC RX2", NULL, "DAP2 Receive" }, + { "IQC TX2", NULL, "IQC RX2" }, + { "CIF2 Transmit", NULL, "IQC TX2" }, +}; + +static struct snd_soc_codec_driver tegra210_iqc_codec = { + .probe = tegra210_iqc_codec_probe, + .dapm_widgets = tegra210_iqc_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_iqc_widgets), + .dapm_routes = tegra210_iqc_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_iqc_routes), + .controls = tegra210_iqc_controls, + .num_controls = ARRAY_SIZE(tegra210_iqc_controls), +}; + +static bool tegra210_iqc_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_IQC_AXBAR_TX_INT_MASK: + case TEGRA210_IQC_AXBAR_TX_INT_SET: + case TEGRA210_IQC_AXBAR_TX_INT_CLEAR: + case TEGRA210_IQC_AXBAR_TX_CIF_CTRL: + case TEGRA210_IQC_ENABLE: + case TEGRA210_IQC_SOFT_RESET: + case TEGRA210_IQC_CG: + case TEGRA210_IQC_CTRL: + case TEGRA210_IQC_CYA: + return true; + default: + return false; + }; +} + +static bool tegra210_iqc_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_IQC_AXBAR_TX_STATUS: + case TEGRA210_IQC_AXBAR_TX_INT_STATUS: + case TEGRA210_IQC_AXBAR_TX_INT_MASK: + case TEGRA210_IQC_AXBAR_TX_INT_SET: + case TEGRA210_IQC_AXBAR_TX_INT_CLEAR: + case TEGRA210_IQC_AXBAR_TX_CIF_CTRL: + case TEGRA210_IQC_ENABLE: + case TEGRA210_IQC_SOFT_RESET: + case TEGRA210_IQC_CG: + case TEGRA210_IQC_STATUS: + case TEGRA210_IQC_INT_STATUS: + case TEGRA210_IQC_CTRL: + case TEGRA210_IQC_TIME_STAMP_STATUS_0: + case TEGRA210_IQC_TIME_STAMP_STATUS_1: + case TEGRA210_IQC_WS_EDGE_STATUS: + case TEGRA210_IQC_CYA: + return true; + default: + return false; + }; +} + +static bool tegra210_iqc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_IQC_AXBAR_TX_CIF_CTRL: + case TEGRA210_IQC_ENABLE: + case TEGRA210_IQC_CTRL: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra210_iqc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_IQC_CYA, + .writeable_reg = tegra210_iqc_wr_reg, + .readable_reg = tegra210_iqc_rd_reg, + .volatile_reg = tegra210_iqc_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra210_iqc_soc_data soc_data_tegra210 = { + .set_audio_cif = tegra210_xbar_set_cif, +}; + +static const struct of_device_id tegra210_iqc_of_match[] = { + { .compatible = "nvidia,tegra210-iqc", .data = &soc_data_tegra210 }, + {}, +}; + +static int tegra210_iqc_platform_probe(struct platform_device *pdev) +{ + struct tegra210_iqc *iqc; + struct resource *mem, *memregion; + void __iomem *regs; + int ret = 0; + const struct of_device_id *match; + struct tegra210_iqc_soc_data *soc_data; + + match = of_match_device(tegra210_iqc_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra210_iqc_soc_data *)match->data; + + iqc = devm_kzalloc(&pdev->dev, sizeof(struct tegra210_iqc), GFP_KERNEL); + if (!iqc) { + dev_err(&pdev->dev, "Can't allocate iqc\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, iqc); + + iqc->soc_data = soc_data; + + iqc->clk_iqc = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(iqc->clk_iqc)) { + dev_err(&pdev->dev, "Can't retrieve iqc clock\n"); + ret = PTR_ERR(iqc->clk_iqc); + goto err; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + 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_clk_put; + } + + regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); + if (!regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_clk_put; + } + + iqc->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra210_iqc_regmap_config); + if (IS_ERR(iqc->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(iqc->regmap); + goto err_clk_put; + } + regcache_cache_only(iqc->regmap, true); + + if (of_property_read_u32(pdev->dev.of_node, + "nvidia,ahub-iqc-id", + &pdev->dev.id) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,ahub-iqc-id\n"); + ret = -ENODEV; + goto err_clk_put; + } + + if (of_property_read_u32(pdev->dev.of_node, + "timestamp-enable", + &iqc->timestamp_enable) < 0) { + dev_err(&pdev->dev, + "Missing property timestamp-enable for IQC\n"); + iqc->timestamp_enable = 1; + } + + if (of_property_read_u32(pdev->dev.of_node, + "data-offset", + &iqc->data_offset) < 0) { + dev_err(&pdev->dev, + "Missing property data-offset for IQC\n"); + iqc->data_offset = 0; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_iqc_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra210_iqc_codec, + tegra210_iqc_dais, + ARRAY_SIZE(tegra210_iqc_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)) + tegra210_iqc_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: + devm_clk_put(&pdev->dev, iqc->clk_iqc); +err: + return ret; +} + +static int tegra210_iqc_platform_remove(struct platform_device *pdev) +{ + struct tegra210_iqc *iqc = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_iqc_runtime_suspend(&pdev->dev); + + devm_clk_put(&pdev->dev, iqc->clk_iqc); + + return 0; +} + +static const struct dev_pm_ops tegra210_iqc_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_iqc_runtime_suspend, + tegra210_iqc_runtime_resume, NULL) +}; + +static struct platform_driver tegra210_iqc_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_iqc_of_match, + .pm = &tegra210_iqc_pm_ops, + }, + .probe = tegra210_iqc_platform_probe, + .remove = tegra210_iqc_platform_remove, +}; +module_platform_driver(tegra210_iqc_driver) + +MODULE_AUTHOR("Arun S L "); +MODULE_DESCRIPTION("Tegra210 IQC ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra210_iqc_of_match); diff --git a/sound/soc/tegra-alt/tegra210_xbar_alt.c b/sound/soc/tegra-alt/tegra210_xbar_alt.c index 579c269c..e0b87961 100644 --- a/sound/soc/tegra-alt/tegra210_xbar_alt.c +++ b/sound/soc/tegra-alt/tegra210_xbar_alt.c @@ -764,6 +764,8 @@ struct of_dev_auxdata tegra210_xbar_auxdata[] = { OF_DEV_AUXDATA("nvidia,tegra210-sfc", 0x702d2600, "tegra210-sfc.3", NULL), OF_DEV_AUXDATA("nvidia,tegra210-mvc", 0x702da000, "tegra210-mvc.0", NULL), OF_DEV_AUXDATA("nvidia,tegra210-mvc", 0x702da200, "tegra210-mvc.1", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-iqc", 0x702de000, "tegra210-iqc.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-iqc", 0x702de200, "tegra210-iqc.1", NULL), OF_DEV_AUXDATA("nvidia,tegra210-dmic", 0x702d4000, "tegra210-dmic.0", NULL), OF_DEV_AUXDATA("nvidia,tegra210-dmic", 0x702d4100, "tegra210-dmic.1", NULL), OF_DEV_AUXDATA("nvidia,tegra210-dmic", 0x702d4200, "tegra210-dmic.2", NULL),