diff --git a/sound/soc/tegra-alt/Kconfig b/sound/soc/tegra-alt/Kconfig index 2a3e3f21..733dd2a7 100644 --- a/sound/soc/tegra-alt/Kconfig +++ b/sound/soc/tegra-alt/Kconfig @@ -94,6 +94,12 @@ config SND_SOC_TEGRA210_I2S_ALT help Say Y or M if you want to add support for Tegra210 I2S module. +config SND_SOC_TEGRA210_DMIC_ALT + tristate "Tegra210 DMIC 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 DMIC module. + config SND_SOC_TEGRA210_AMX_ALT tristate "Tegra210 AMX driver" depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_210 @@ -172,6 +178,7 @@ config SND_SOC_TEGRA_GRENADA_ALT select SND_SOC_TEGRA210_ADMA_ALT select SND_SOC_TEGRA210_ADMAIF_ALT select SND_SOC_TEGRA210_I2S_ALT + select SND_SOC_TEGRA210_DMIC_ALT select SND_SOC_TEGRA210_AMX_ALT select SND_SOC_TEGRA210_ADX_ALT select SND_SOC_TEGRA210_MIXER_ALT diff --git a/sound/soc/tegra-alt/Makefile b/sound/soc/tegra-alt/Makefile index 0fdfe541..9a5765dc 100644 --- a/sound/soc/tegra-alt/Makefile +++ b/sound/soc/tegra-alt/Makefile @@ -16,6 +16,7 @@ snd-soc-tegra124-alt-afc-objs := tegra124_afc_alt.o snd-soc-tegra210-alt-admaif-objs := tegra210_admaif_alt.o snd-soc-tegra210-alt-xbar-objs := tegra210_xbar_alt.o snd-soc-tegra210-alt-i2s-objs := tegra210_i2s_alt.o +snd-soc-tegra210-alt-dmic-objs := tegra210_dmic_alt.o snd-soc-tegra210-alt-amx-objs := tegra210_amx_alt.o snd-soc-tegra210-alt-adx-objs := tegra210_adx_alt.o snd-soc-tegra210-alt-mixer-objs := tegra210_mixer_alt.o @@ -40,6 +41,7 @@ obj-$(CONFIG_SND_SOC_TEGRA124_AFC_ALT) += snd-soc-tegra124-alt-afc.o obj-$(CONFIG_SND_SOC_TEGRA210_ADMAIF_ALT) += snd-soc-tegra210-alt-admaif.o obj-$(CONFIG_SND_SOC_TEGRA210_XBAR_ALT) += snd-soc-tegra210-alt-xbar.o obj-$(CONFIG_SND_SOC_TEGRA210_I2S_ALT) += snd-soc-tegra210-alt-i2s.o +obj-$(CONFIG_SND_SOC_TEGRA210_DMIC_ALT) += snd-soc-tegra210-alt-dmic.o obj-$(CONFIG_SND_SOC_TEGRA210_AMX_ALT) += snd-soc-tegra210-alt-amx.o obj-$(CONFIG_SND_SOC_TEGRA210_ADX_ALT) += snd-soc-tegra210-alt-adx.o obj-$(CONFIG_SND_SOC_TEGRA210_MIXER_ALT) += snd-soc-tegra210-alt-mixer.o diff --git a/sound/soc/tegra-alt/tegra210_dmic_alt.c b/sound/soc/tegra-alt/tegra210_dmic_alt.c new file mode 100644 index 00000000..aa2d3550 --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_dmic_alt.c @@ -0,0 +1,429 @@ +/* + * tegra210_dmic_alt.c - Tegra210 DMIC 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 "tegra210_xbar_alt.h" +#include "tegra210_dmic_alt.h" +#include "ahub_unit_fpga_clock.h" + +#define DRV_NAME "tegra210-dmic" + +static int tegra210_dmic_runtime_suspend(struct device *dev) +{ + struct tegra210_dmic *dmic = dev_get_drvdata(dev); + regcache_cache_only(dmic->regmap, true); + +#ifndef CONFIG_MACH_GRENADA + clk_disable_unprepare(dmic->clk_dmic); +#endif + return 0; +} + +static int tegra210_dmic_runtime_resume(struct device *dev) +{ + struct tegra210_dmic *dmic = dev_get_drvdata(dev); + +#ifndef CONFIG_MACH_GRENADA + int ret; + + ret = clk_prepare_enable(dmic->clk_dmic); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } +#endif + + regcache_cache_only(dmic->regmap, false); + 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 device *dev = dai->dev; + struct tegra210_dmic *dmic = snd_soc_dai_get_drvdata(dai); + int channels, srate, dmic_clk, osr = TEGRA210_DMIC_OSR_64; + struct tegra210_xbar_cif_conf cif_conf; + + channels = params_channels(params); + srate = params_rate(params); + dmic_clk = (1 << (6+osr)) * (srate/2); + +#ifdef CONFIG_MACH_GRENADA + program_dmic_gpio(); + program_dmic_clk(dmic_clk); +#endif + + regmap_update_bits(dmic->regmap, + TEGRA210_DMIC_CTRL, + TEGRA210_DMIC_CTRL_OSR_MASK, + osr << TEGRA210_DMIC_CTRL_OSR_SHIFT); + + regmap_update_bits(dmic->regmap, + TEGRA210_DMIC_DBG_CTRL, + TEGRA210_DMIC_DBG_CTRL_SC_ENABLE, + TEGRA210_DMIC_DBG_CTRL_SC_ENABLE); + + regmap_update_bits(dmic->regmap, + TEGRA210_DMIC_CTRL, + TEGRA210_DMIC_CTRL_CHANNEL_SELECT_MASK, + ((1 << channels) - 1) << + TEGRA210_DMIC_CTRL_CHANNEL_SELECT_SHIFT); + + cif_conf.threshold = 0; + cif_conf.audio_channels = channels; + cif_conf.client_channels = channels; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + cif_conf.audio_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; + } + + cif_conf.client_bits = TEGRA210_AUDIOCIF_BITS_24; + cif_conf.expand = 0; + cif_conf.stereo_conv = 0; + cif_conf.replicate = 0; + cif_conf.truncate = 0; + cif_conf.mono_conv = 0; + + dmic->soc_data->set_audio_cif(dmic->regmap, TEGRA210_DMIC_TX_CIF_CTRL, + &cif_conf); + + return 0; +} + +static int tegra210_dmic_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra210_dmic *dmic = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = dmic->regmap; + ret = snd_soc_codec_set_cache_io(codec, 32, 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_dmic_dai_ops = { + .hw_params = tegra210_dmic_hw_params, +}; + +static struct snd_soc_dai_driver tegra210_dmic_dais[] = { + { + .name = "CIF", + .capture = { + .stream_name = "DMIC Transmit", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra210_dmic_dai_ops, + .symmetric_rates = 1, + }, + { + .name = "DAP", + .playback = { + .stream_name = "DMIC Receive", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra210_dmic_dai_ops, + .symmetric_rates = 1, + } +}; + +static const struct snd_soc_dapm_widget tegra210_dmic_widgets[] = { + SND_SOC_DAPM_AIF_OUT("DMIC TX", NULL, 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_IN("DMIC RX", NULL, 0, TEGRA210_DMIC_ENABLE, + TEGRA210_DMIC_ENABLE_EN_SHIFT, 0), +}; + +static const struct snd_soc_dapm_route tegra210_dmic_routes[] = { + { "DMIC RX", NULL, "DMIC Receive" }, + { "DMIC TX", NULL, "DMIC RX" }, + { "DMIC Transmit", NULL, "DMIC TX" }, +}; + +static struct snd_soc_codec_driver tegra210_dmic_codec = { + .probe = tegra210_dmic_codec_probe, + .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), +}; + +/* Regmap callback functions */ +static bool tegra210_dmic_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_DMIC_TX_INT_MASK: + case TEGRA210_DMIC_TX_INT_SET: + case TEGRA210_DMIC_TX_INT_CLEAR: + case TEGRA210_DMIC_TX_CIF_CTRL: + + case TEGRA210_DMIC_ENABLE: + case TEGRA210_DMIC_SOFT_RESET: + case TEGRA210_DMIC_CG: + case TEGRA210_DMIC_CTRL: + return true; + default: + if (((reg % 4) == 0) && (reg >= TEGRA210_DMIC_DBG_CTRL) && + (reg <= TEGRA210_DMIC_CORRECTION_BIQUAD_1_COEF_4)) + return true; + else + return false; + }; +} + +static bool tegra210_dmic_rd_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_MASK: + case TEGRA210_DMIC_TX_INT_SET: + case TEGRA210_DMIC_TX_INT_CLEAR: + case TEGRA210_DMIC_TX_CIF_CTRL: + + case TEGRA210_DMIC_ENABLE: + case TEGRA210_DMIC_SOFT_RESET: + case TEGRA210_DMIC_CG: + case TEGRA210_DMIC_STATUS: + case TEGRA210_DMIC_INT_STATUS: + case TEGRA210_DMIC_CTRL: + return true; + default: + if (((reg % 4) == 0) && (reg >= TEGRA210_DMIC_DBG_CTRL) && + (reg <= TEGRA210_DMIC_CORRECTION_BIQUAD_1_COEF_4)) + return true; + else + 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_CORRECTION_BIQUAD_1_COEF_4, + .writeable_reg = tegra210_dmic_wr_reg, + .readable_reg = tegra210_dmic_rd_reg, + .volatile_reg = tegra210_dmic_volatile_reg, + .precious_reg = NULL, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra210_dmic_soc_data soc_data_tegra210 = { + .set_audio_cif = tegra210_xbar_set_cif, +}; + +static const struct of_device_id tegra210_dmic_of_match[] = { + { .compatible = "nvidia,tegra210-dmic", .data = &soc_data_tegra210 }, + {}, +}; + +static int tegra210_dmic_platform_probe(struct platform_device *pdev) +{ + struct tegra210_dmic *dmic; + struct resource *mem, *memregion; + void __iomem *regs; + int ret = 0; + const struct of_device_id *match; + struct tegra210_dmic_soc_data *soc_data; + + match = of_match_device(tegra210_dmic_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra210_dmic_soc_data *)match->data; + + dmic = devm_kzalloc(&pdev->dev, sizeof(struct tegra210_dmic), GFP_KERNEL); + if (!dmic) { + dev_err(&pdev->dev, "Can't allocate dmic\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, dmic); + + dmic->soc_data = soc_data; + +#ifndef CONFIG_MACH_GRENADA + dmic->clk_dmic = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dmic->clk_dmic)) { + dev_err(&pdev->dev, "Can't retrieve dmic clock\n"); + ret = PTR_ERR(dmic->clk_dmic); + goto err; + } +#endif + + 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; + } + + dmic->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra210_dmic_regmap_config); + if (IS_ERR(dmic->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(dmic->regmap); + goto err_clk_put; + } + regcache_cache_only(dmic->regmap, true); + + if (of_property_read_u32(pdev->dev.of_node, + "nvidia,ahub-dmic-id", + &pdev->dev.id) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,ahub-dmic-id\n"); + ret = -ENODEV; + goto err_clk_put; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_dmic_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra210_dmic_codec, + tegra210_dmic_dais, + ARRAY_SIZE(tegra210_dmic_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_dmic_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, dmic->clk_dmic); +#endif +err: + return ret; +} + +static int tegra210_dmic_platform_remove(struct platform_device *pdev) +{ + struct tegra210_dmic *dmic; + + dmic = dev_get_drvdata(&pdev->dev); + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_dmic_runtime_suspend(&pdev->dev); + +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, dmic->clk_dmic); +#endif + 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) +}; + +static struct platform_driver tegra210_dmic_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_dmic_of_match, + .pm = &tegra210_dmic_pm_ops, + }, + .probe = tegra210_dmic_platform_probe, + .remove = tegra210_dmic_platform_remove, +}; +module_platform_driver(tegra210_dmic_driver) + +MODULE_AUTHOR("Rahul Mittal "); +MODULE_DESCRIPTION("Tegra210 DMIC ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra210_dmic_of_match); diff --git a/sound/soc/tegra-alt/tegra210_xbar_alt.c b/sound/soc/tegra-alt/tegra210_xbar_alt.c index 68648ef9..579c269c 100644 --- a/sound/soc/tegra-alt/tegra210_xbar_alt.c +++ b/sound/soc/tegra-alt/tegra210_xbar_alt.c @@ -606,6 +606,9 @@ static const struct snd_soc_dapm_widget tegra210_xbar_widgets[] = { { name " Mux", "IQC1-2", "IQC1-2 RX" }, \ { name " Mux", "IQC2-1", "IQC2-1 RX" }, \ { name " Mux", "IQC2-2", "IQC2-2 RX" }, \ + { name " Mux", "DMIC1", "DMIC1 RX" }, \ + { name " Mux", "DMIC2", "DMIC2 RX" }, \ + { name " Mux", "DMIC3", "DMIC3 RX" }, \ { name " Mux", "AMX1", "AMX1 RX" }, \ { name " Mux", "ADX1-1", "ADX1-1 RX" }, \ { name " Mux", "ADX1-2", "ADX1-2 RX" }, \ @@ -761,6 +764,9 @@ 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-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), OF_DEV_AUXDATA("nvidia,tegra210-amixer", 0x702dbb00, "tegra210-mixer", NULL), OF_DEV_AUXDATA("nvidia,tegra210-spdif", 0x702d6000, "tegra210-spdif", NULL), {}