diff --git a/sound/soc/tegra-alt/Kconfig b/sound/soc/tegra-alt/Kconfig index f2a20096..fb47ff88 100644 --- a/sound/soc/tegra-alt/Kconfig +++ b/sound/soc/tegra-alt/Kconfig @@ -1,8 +1,8 @@ config SND_SOC_TEGRA_ALT tristate "Alternative DAPM-based SoC audio support for the Tegra System-on-Chip" - depends on ARCH_TEGRA && TEGRA20_APB_DMA + depends on ARCH_TEGRA select REGMAP_MMIO - select SND_SOC_DMAENGINE_PCM if TEGRA20_APB_DMA + select SND_SOC_DMAENGINE_PCM help Say Y or M here if you want support for SoC audio on Tegra, using the alternative driver that exposes to user-space the full routing capabilities @@ -18,18 +18,35 @@ config SND_SOC_TEGRA_ALT_114_OR_LATER depends on SND_SOC_TEGRA_ALT depends on ARCH_TEGRA_11x_SOC || ARCH_TEGRA_12x_SOC +config SND_SOC_TEGRA_ALT_210 + def_bool y + depends on SND_SOC_TEGRA_ALT + depends on ARCH_TEGRA_APE + config SND_SOC_TEGRA30_XBAR_ALT tristate "Tegra30 XBAR driver" depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER help Say Y or M if you want to add support for Tegra30 XBAR module. +config SND_SOC_TEGRA210_XBAR_ALT + tristate "Tegra210 XBAR 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 XBAR module. + config SND_SOC_TEGRA30_APBIF_ALT tristate "Tegra30 APBIF driver" depends on SND_SOC_TEGRA30_XBAR_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER help Say Y or M if you want to add support for Tegra30 APBIF module. +config SND_SOC_TEGRA210_ADMAIF_ALT + tristate "Tegra210 ADMIF 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 APBIF module. + config SND_SOC_TEGRA30_I2S_ALT tristate "Tegra30 I2S driver" depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER @@ -54,6 +71,54 @@ config SND_SOC_TEGRA114_ADX_ALT help Say Y or M if you want to add support for Tegra114 ADX module. +config SND_SOC_TEGRA210_I2S_ALT + tristate "Tegra210 I2S 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 I2S module. + +config SND_SOC_TEGRA210_AMX_ALT + tristate "Tegra210 AMX 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 AMX module. + +config SND_SOC_TEGRA210_ADX_ALT + tristate "Tegra210 ADX 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 ADX module. + +config SND_SOC_TEGRA210_MIXER_ALT + tristate "Tegra210 MIXER 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 MIXER module. + +config SND_SOC_TEGRA210_SFC_ALT + tristate "Tegra210 SFC 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 SFC module. + +config SND_SOC_TEGRA210_AFC_ALT + tristate "Tegra210 AFC 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 AFC module. + +config SND_SOC_TEGRA210_MVC_ALT + tristate "Tegra210 MVC 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 MVC module. + +config SND_SOC_TEGRA210_SPDIF_ALT + tristate "Tegra210 SPDIF 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 SPDIF module. + config SND_SOC_TEGRA_VCM30T124_ALT tristate "SoC Audio support for VCM30_T124" depends on SND_SOC_TEGRA_ALT diff --git a/sound/soc/tegra-alt/Makefile b/sound/soc/tegra-alt/Makefile index 176b10cd..f4e1ead9 100644 --- a/sound/soc/tegra-alt/Makefile +++ b/sound/soc/tegra-alt/Makefile @@ -11,15 +11,36 @@ snd-soc-tegra30-alt-i2s-objs := tegra30_i2s_alt.o snd-soc-tegra30-alt-spdif-objs := tegra30_spdif_alt.o snd-soc-tegra114-alt-amx-objs := tegra114_amx_alt.o snd-soc-tegra114-alt-adx-objs := tegra114_adx_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-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 +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-spdif-objs := tegra210_spdif_alt.o +snd-soc-tegra210-alt-fpga-clock-objs := ahub_unit_fpga_clock.o -obj-$(CONFIG_SND_SOC_TEGRA_ALT) += snd-soc-tegra-alt-pcm.o obj-$(CONFIG_SND_SOC_TEGRA_ALT) += snd-soc-tegra-alt-utils.o +obj-$(CONFIG_SND_SOC_TEGRA_ALT) += snd-soc-tegra-alt-pcm.o obj-$(CONFIG_SND_SOC_TEGRA30_APBIF_ALT) += snd-soc-tegra30-alt-apbif.o obj-$(CONFIG_SND_SOC_TEGRA30_XBAR_ALT) += snd-soc-tegra30-alt-xbar.o obj-$(CONFIG_SND_SOC_TEGRA30_I2S_ALT) += snd-soc-tegra30-alt-i2s.o obj-$(CONFIG_SND_SOC_TEGRA30_SPDIF_ALT) += snd-soc-tegra30-alt-spdif.o obj-$(CONFIG_SND_SOC_TEGRA114_AMX_ALT) += snd-soc-tegra114-alt-amx.o obj-$(CONFIG_SND_SOC_TEGRA114_ADX_ALT) += snd-soc-tegra114-alt-adx.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_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 +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_SPDIF_ALT) += snd-soc-tegra210-alt-spdif.o # Tegra machine Support snd-soc-tegra-alt-vcm30t124-objs := tegra_vcm30t124_alt.o diff --git a/sound/soc/tegra-alt/tegra210_admaif_alt.c b/sound/soc/tegra-alt/tegra210_admaif_alt.c new file mode 100644 index 00000000..d343ae5f --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_admaif_alt.c @@ -0,0 +1,604 @@ +/* + * tegra210_admaif_alt.c - Tegra ADMAIF 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 "tegra_pcm_alt.h" +#include "tegra210_xbar_alt.h" +#include "tegra210_admaif_alt.h" + +#define DRV_NAME "tegra210-ape-admaif" + +static bool tegra210_admaif_wr_reg(struct device *dev, unsigned int reg) +{ + reg = reg % TEGRA210_ADMAIF_CHANNEL_REG_STRIDE; + + switch (reg) { + case TEGRA210_ADMAIF_XBAR_TX_ENABLE: + case TEGRA210_ADMAIF_XBAR_TX_STATUS: + case TEGRA210_ADMAIF_XBAR_TX_FIFO_CTRL: + case TEGRA210_ADMAIF_XBAR_TX_SOFT_RESET: + case TEGRA210_ADMAIF_CHAN_ACIF_TX_CTRL: + case TEGRA210_ADMAIF_XBAR_RX_ENABLE: + case TEGRA210_ADMAIF_XBAR_RX_FIFO_CTRL: + case TEGRA210_ADMAIF_XBAR_RX_SOFT_RESET: + case TEGRA210_ADMAIF_CHAN_ACIF_RX_CTRL: + case TEGRA210_ADMAIF_GLOBAL_ENABLE: + return true; + default: + break; + }; + + return false; +} + +static bool tegra210_admaif_rd_reg(struct device *dev, unsigned int reg) +{ + reg = reg % TEGRA210_ADMAIF_CHANNEL_REG_STRIDE; + + switch (reg) { + case TEGRA210_ADMAIF_XBAR_RX_STATUS: + case TEGRA210_ADMAIF_XBAR_RX_INT_STATUS: + case TEGRA210_ADMAIF_XBAR_RX_ENABLE: + case TEGRA210_ADMAIF_XBAR_RX_SOFT_RESET: + case TEGRA210_ADMAIF_XBAR_RX_FIFO_CTRL: + case TEGRA210_ADMAIF_CHAN_ACIF_RX_CTRL: + case TEGRA210_ADMAIF_XBAR_TX_STATUS: + case TEGRA210_ADMAIF_XBAR_TX_INT_STATUS: + case TEGRA210_ADMAIF_XBAR_TX_ENABLE: + case TEGRA210_ADMAIF_XBAR_TX_SOFT_RESET: + case TEGRA210_ADMAIF_XBAR_TX_FIFO_CTRL: + case TEGRA210_ADMAIF_CHAN_ACIF_TX_CTRL: + case TEGRA210_ADMAIF_GLOBAL_ENABLE: + return true; + default: + return false; + }; +} + +static bool tegra210_admaif_volatile_reg(struct device *dev, unsigned int reg) +{ + reg = reg % TEGRA210_ADMAIF_CHANNEL_REG_STRIDE; + + switch (reg) { + case TEGRA210_ADMAIF_XBAR_RX_STATUS: + case TEGRA210_ADMAIF_XBAR_TX_STATUS: + case TEGRA210_ADMAIF_XBAR_RX_INT_STATUS: + case TEGRA210_ADMAIF_XBAR_TX_INT_STATUS: + case TEGRA210_ADMAIF_XBAR_RX_SOFT_RESET: + case TEGRA210_ADMAIF_XBAR_TX_SOFT_RESET: + return true; + default: + break; + }; + + return false; +} + +static const struct regmap_config tegra210_admaif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_ADMAIF_LAST_REG, + .writeable_reg = tegra210_admaif_wr_reg, + .readable_reg = tegra210_admaif_rd_reg, + .volatile_reg = tegra210_admaif_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static void tegra210_admaif_global_enable(struct tegra210_admaif *admaif, + int enable) +{ + if (enable) { + regmap_update_bits(admaif->regmap, + TEGRA210_ADMAIF_GLOBAL_ENABLE, 1, 1); + admaif->refcnt++; + } else { + admaif->refcnt--; + + if (!admaif->refcnt) + regmap_update_bits(admaif->regmap, + TEGRA210_ADMAIF_GLOBAL_ENABLE, 1, 0); + } +} + +static int tegra210_admaif_sw_reset(struct snd_soc_dai *dai, + int direction, int timeout) +{ + struct tegra210_admaif *admaif = snd_soc_dai_get_drvdata(dai); + unsigned int sw_reset_reg, val; + int wait = timeout; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + sw_reset_reg = TEGRA210_ADMAIF_XBAR_TX_SOFT_RESET + + (dai->id * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + } else { + sw_reset_reg = TEGRA210_ADMAIF_XBAR_RX_SOFT_RESET + + (dai->id * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + } + + regmap_update_bits(admaif->regmap, sw_reset_reg, 1, 1); + + do { + regmap_read(admaif->regmap, sw_reset_reg, &val); + wait--; + if (!wait) + return -EINVAL; + } while (val & 0x00000001); + + return 0; +} + +static int tegra210_admaif_runtime_suspend(struct device *dev) +{ + struct tegra210_admaif *admaif = dev_get_drvdata(dev); + + regcache_cache_only(admaif->regmap, true); + + clk_disable(admaif->clk); + + return 0; +} + +static int tegra210_admaif_runtime_resume(struct device *dev) +{ + struct tegra210_admaif *admaif = dev_get_drvdata(dev); + int ret; + + ret = clk_enable(admaif->clk); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + regcache_cache_only(admaif->regmap, false); + + return 0; +} + +static int tegra210_admaif_set_pack_mode(struct regmap *map, unsigned int reg, + int valid_bit) +{ + switch (valid_bit) { + case DATA_8BIT: + regmap_update_bits(map, reg, + TEGRA210_ADMAIF_CHAN_ACIF_CTRL_PACK8_EN_MASK, + TEGRA210_ADMAIF_CHAN_ACIF_CTRL_PACK8_EN); + regmap_update_bits(map, reg, + TEGRA210_ADMAIF_CHAN_ACIF_CTRL_PACK16_EN_MASK, + 0); + break; + case DATA_16BIT: + regmap_update_bits(map, reg, + TEGRA210_ADMAIF_CHAN_ACIF_CTRL_PACK16_EN_MASK, + TEGRA210_ADMAIF_CHAN_ACIF_CTRL_PACK16_EN); + regmap_update_bits(map, reg, + TEGRA210_ADMAIF_CHAN_ACIF_CTRL_PACK8_EN_MASK, + 0); + break; + case DATA_32BIT: + regmap_update_bits(map, reg, + TEGRA210_ADMAIF_CHAN_ACIF_CTRL_PACK16_EN_MASK, + 0); + regmap_update_bits(map, reg, + TEGRA210_ADMAIF_CHAN_ACIF_CTRL_PACK8_EN_MASK, + 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra210_admaif_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_admaif *admaif = snd_soc_dai_get_drvdata(dai); + struct tegra210_xbar_cif_conf cif_conf; + unsigned int reg, fifo_ctrl, fifo_size; + int valid_bit, ret; + + cif_conf.audio_channels = params_channels(params); + cif_conf.client_channels = params_channels(params); + + 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; + valid_bit = DATA_16BIT; + break; + case SNDRV_PCM_FORMAT_S32_LE: + cif_conf.audio_bits = TEGRA210_AUDIOCIF_BITS_32; + cif_conf.client_bits = TEGRA210_AUDIOCIF_BITS_32; + valid_bit = DATA_32BIT; + break; + default: + dev_err(dev, "Wrong format!\n"); + return -EINVAL; + } + + cif_conf.threshold = 0; + cif_conf.expand = 0; + cif_conf.stereo_conv = 0; + cif_conf.replicate = 0; + cif_conf.truncate = 0; + cif_conf.mono_conv = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg = TEGRA210_ADMAIF_CHAN_ACIF_TX_CTRL + + (dai->id * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + fifo_ctrl = TEGRA210_ADMAIF_XBAR_TX_FIFO_CTRL + + (dai->id * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + fifo_size = 3; + } else { + reg = TEGRA210_ADMAIF_CHAN_ACIF_RX_CTRL + + (dai->id * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + fifo_ctrl = TEGRA210_ADMAIF_XBAR_RX_FIFO_CTRL + + (dai->id * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + fifo_size = 3; + } + + /* HW needs sw reset to make sure previous transaction be clean */ + ret = tegra210_admaif_sw_reset(dai, substream->stream, 0xffff); + if (ret) { + dev_err(dev, "Failed at sw reset\n"); + return ret; + } + + tegra210_admaif_set_pack_mode(admaif->regmap, reg, valid_bit); + admaif->soc_data->set_audio_cif(admaif->regmap, reg, &cif_conf); + + regmap_update_bits(admaif->regmap, fifo_ctrl, + TEGRA210_ADMAIF_XBAR_DMA_FIFO_SIZE_MASK, + fifo_size << TEGRA210_ADMAIF_XBAR_DMA_FIFO_SIZE_SHIFT); + + return 0; +} + +static void tegra210_admaif_start_playback(struct snd_soc_dai *dai) +{ + struct tegra210_admaif *admaif = snd_soc_dai_get_drvdata(dai); + unsigned int reg; + + tegra210_admaif_global_enable(admaif, 1); + + reg = TEGRA210_ADMAIF_XBAR_TX_ENABLE + + (dai->id * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + regmap_update_bits(admaif->regmap, reg, + TEGRA210_ADMAIF_XBAR_TX_ENABLE_MASK, + TEGRA210_ADMAIF_XBAR_TX_EN); +} + +static void tegra210_admaif_stop_playback(struct snd_soc_dai *dai) +{ + struct tegra210_admaif *admaif = snd_soc_dai_get_drvdata(dai); + unsigned int reg; + + tegra210_admaif_global_enable(admaif, 0); + reg = TEGRA210_ADMAIF_XBAR_TX_ENABLE + + (dai->id * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + regmap_update_bits(admaif->regmap, reg, + TEGRA210_ADMAIF_XBAR_TX_ENABLE_MASK, + 0); +} + +static void tegra210_admaif_start_capture(struct snd_soc_dai *dai) +{ + struct tegra210_admaif *admaif = snd_soc_dai_get_drvdata(dai); + unsigned int reg; + + tegra210_admaif_global_enable(admaif, 1); + reg = TEGRA210_ADMAIF_XBAR_RX_ENABLE + + (dai->id * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + regmap_update_bits(admaif->regmap, reg, + TEGRA210_ADMAIF_XBAR_RX_ENABLE_MASK, + TEGRA210_ADMAIF_XBAR_RX_EN); +} + +static void tegra210_admaif_stop_capture(struct snd_soc_dai *dai) +{ + struct tegra210_admaif *admaif = snd_soc_dai_get_drvdata(dai); + unsigned int reg; + + tegra210_admaif_global_enable(admaif, 0); + reg = TEGRA210_ADMAIF_XBAR_RX_ENABLE + + (dai->id * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + regmap_update_bits(admaif->regmap, reg, + TEGRA210_ADMAIF_XBAR_RX_ENABLE_MASK, + 0); +} + +static int tegra210_admaif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra210_admaif_start_playback(dai); + else + tegra210_admaif_start_capture(dai); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra210_admaif_stop_playback(dai); + else + tegra210_admaif_stop_capture(dai); + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops tegra210_admaif_dai_ops = { + .hw_params = tegra210_admaif_hw_params, + .trigger = tegra210_admaif_trigger, +}; + +static int tegra210_admaif_dai_probe(struct snd_soc_dai *dai) +{ + struct tegra210_admaif *admaif = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &admaif->capture_dma_data[dai->id]; + dai->playback_dma_data = &admaif->playback_dma_data[dai->id]; + + return 0; +} + +#define ADMAIF_DAI(id) \ + { \ + .name = "ADMAIF" #id, \ + .probe = tegra210_admaif_dai_probe, \ + .playback = { \ + .stream_name = "Playback " #id, \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .capture = { \ + .stream_name = "Capture " #id, \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .ops = &tegra210_admaif_dai_ops, \ + } + +static struct snd_soc_dai_driver tegra210_admaif_dais[10] = { + ADMAIF_DAI(1), + ADMAIF_DAI(2), + ADMAIF_DAI(3), + ADMAIF_DAI(4), + ADMAIF_DAI(5), + ADMAIF_DAI(6), + ADMAIF_DAI(7), + ADMAIF_DAI(8), + ADMAIF_DAI(9), + ADMAIF_DAI(10), +}; + +static const struct snd_soc_component_driver tegra210_admaif_dai_driver = { + .name = DRV_NAME, +}; + +static struct tegra210_admaif_soc_data soc_data_tegra210 = { + .num_ch = 10, + .set_audio_cif = tegra210_xbar_set_cif, +}; + +static const struct of_device_id tegra210_admaif_of_match[] = { + { .compatible = "nvidia,tegra210-admaif", .data = &soc_data_tegra210 }, + {}, +}; + +static int tegra210_admaif_probe(struct platform_device *pdev) +{ + int i; + + int ret; + struct tegra210_admaif *admaif; + void __iomem *regs; + struct resource *res; + const struct of_device_id *match; + struct tegra210_admaif_soc_data *soc_data; + + match = of_match_device(tegra210_admaif_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + return -ENODEV; + } + soc_data = (struct tegra210_admaif_soc_data *)match->data; + + admaif = devm_kzalloc(&pdev->dev, sizeof(*admaif), GFP_KERNEL); + if (!admaif) { + dev_err(&pdev->dev, "Can't allocate tegra210_admaif\n"); + ret = -ENOMEM; + goto err; + } + + dev_set_drvdata(&pdev->dev, admaif); + + admaif->refcnt = 0; + + admaif->soc_data = soc_data; + + admaif->capture_dma_data = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_alt_pcm_dma_params) * + admaif->soc_data->num_ch, + GFP_KERNEL); + if (!admaif->capture_dma_data) { + dev_err(&pdev->dev, "Can't allocate tegra_alt_pcm_dma_params\n"); + ret = -ENOMEM; + goto err; + } + + admaif->playback_dma_data = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_alt_pcm_dma_params) * + admaif->soc_data->num_ch, + GFP_KERNEL); + if (!admaif->playback_dma_data) { + dev_err(&pdev->dev, "Can't allocate tegra_alt_pcm_dma_params\n"); + ret = -ENOMEM; + goto err; + } + + admaif->clk = devm_clk_get(&pdev->dev, "admaif"); + if (IS_ERR(admaif->clk)) { + dev_err(&pdev->dev, "Can't retrieve clock\n"); + ret = PTR_ERR(admaif->clk); + goto err; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "No memory resource for admaif\n"); + ret = -ENODEV; + goto err_clk_put; + } + + regs = devm_request_and_ioremap(&pdev->dev, res); + if (!regs) { + dev_err(&pdev->dev, "request/iomap region failed\n"); + ret = -ENODEV; + goto err_clk_put; + } + + admaif->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra210_admaif_regmap_config); + if (IS_ERR(admaif->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(admaif->regmap); + goto err_clk_put; + } + regcache_cache_only(admaif->regmap, true); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_admaif_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + for (i = 0; i < admaif->soc_data->num_ch; i++) { + admaif->playback_dma_data[i].addr = res->start + + TEGRA210_ADMAIF_XBAR_TX_FIFO_WRITE + + (i * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + + admaif->capture_dma_data[i].addr = res->start + + TEGRA210_ADMAIF_XBAR_RX_FIFO_READ + + (i * TEGRA210_ADMAIF_CHANNEL_REG_STRIDE); + + admaif->playback_dma_data[i].wrap = 4; + admaif->playback_dma_data[i].width = 32; + admaif->playback_dma_data[i].req_sel = i + 1; + + admaif->capture_dma_data[i].wrap = 4; + admaif->capture_dma_data[i].width = 32; + admaif->capture_dma_data[i].req_sel = i + 1; + } + + ret = snd_soc_register_component(&pdev->dev, + &tegra210_admaif_dai_driver, + tegra210_admaif_dais, + ARRAY_SIZE(tegra210_admaif_dais)); + if (ret) { + dev_err(&pdev->dev, "Could not register DAIs %d: %d\n", + i, ret); + ret = -ENOMEM; + goto err_suspend; + } + + ret = tegra_alt_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_dais; + } + + return 0; + +err_unregister_dais: + snd_soc_unregister_component(&pdev->dev); +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_admaif_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: + devm_clk_put(&pdev->dev, admaif->clk); +err: + return ret; +} + +static int tegra210_admaif_remove(struct platform_device *pdev) +{ + struct tegra210_admaif *admaif = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_component(&pdev->dev); + + tegra_alt_pcm_platform_unregister(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_admaif_runtime_suspend(&pdev->dev); + + devm_clk_put(&pdev->dev, admaif->clk); + + return 0; +} + +static const struct dev_pm_ops tegra210_admaif_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_admaif_runtime_suspend, + tegra210_admaif_runtime_resume, NULL) +}; + +static struct platform_driver tegra210_admaif_driver = { + .probe = tegra210_admaif_probe, + .remove = tegra210_admaif_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_admaif_of_match, + .pm = &tegra210_admaif_pm_ops, + }, +}; +module_platform_driver(tegra210_admaif_driver); + +MODULE_AUTHOR("Songhee Baek "); +MODULE_DESCRIPTION("Tegra210 ADMAIF driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra-alt/tegra210_adx_alt.c b/sound/soc/tegra-alt/tegra210_adx_alt.c new file mode 100644 index 00000000..9ffe3071 --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_adx_alt.c @@ -0,0 +1,659 @@ +/* + * tegra210_adx_alt.c - Tegra210 ADX 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 "tegra210_xbar_alt.h" +#include "tegra210_adx_alt.h" + +#define DRV_NAME "tegra210-adx" + +/** + * tegra210_adx_enable_outstream - enable output stream + * @adx: struct of tegra210_adx + * @stream_id: adx output stream id for enabling + */ +static void tegra210_adx_enable_outstream(struct tegra210_adx *adx, + unsigned int stream_id) +{ + int reg; + + reg = TEGRA210_ADX_CTRL; + + regmap_update_bits(adx->regmap, reg, + TEGRA210_ADX_TX_ENABLE << stream_id, + TEGRA210_ADX_TX_ENABLE << stream_id); +} + +/** + * tegra210_adx_disable_outstream - disable output stream + * @adx: struct of tegra210_adx + * @stream_id: adx output stream id for disabling + */ +static void tegra210_adx_disable_outstream(struct tegra210_adx *adx, + unsigned int stream_id) +{ + int reg; + + reg = TEGRA210_ADX_CTRL; + + regmap_update_bits(adx->regmap, reg, + TEGRA210_ADX_TX_ENABLE << stream_id, + TEGRA210_ADX_TX_DISABLE); +} + +/** + * tegra210_adx_set_in_byte_mask - set byte mask for input frame + * @adx: struct of tegra210_adx + * @mask1: enable for bytes 31 ~ 0 of input frame + * @mask2: enable for bytes 63 ~ 32 of input frame + */ +static void tegra210_adx_set_in_byte_mask(struct tegra210_adx *adx, + unsigned int mask1, + unsigned int mask2) +{ + regmap_write(adx->regmap, TEGRA210_ADX_IN_BYTE_EN0, mask1); + regmap_write(adx->regmap, TEGRA210_ADX_IN_BYTE_EN1, mask2); +} + +/** + * tegra210_adx_set_map_table - set map table not RAM + * @adx: struct of tegra210_adx + * @out_byte_addr: byte address in one frame + * @stream_id: input stream id + * @nth_word: n-th word in the input stream + * @nth_byte: n-th byte in the word + */ +static void tegra210_adx_set_map_table(struct tegra210_adx *adx, + unsigned int out_byte_addr, + unsigned int stream_id, + unsigned int nth_word, + unsigned int nth_byte) +{ + unsigned char *bytes_map = (unsigned char *)&adx->map; + + bytes_map[out_byte_addr] = + (stream_id << TEGRA210_ADX_MAP_STREAM_NUMBER_SHIFT) | + (nth_word << TEGRA210_ADX_MAP_WORD_NUMBER_SHIFT) | + (nth_byte << TEGRA210_ADX_MAP_BYTE_NUMBER_SHIFT); +} + +/** + * tegra210_adx_write_map_ram - write map information in RAM + * @adx: struct of tegra210_adx + * @addr: n-th word of input stream + * @val : bytes mapping information of the word + */ +static void tegra210_adx_write_map_ram(struct tegra210_adx *adx, + unsigned int addr, + unsigned int val) +{ + unsigned int reg; + + regmap_write(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL, + (addr << TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL_RAM_ADDR_SHIFT)); + + regmap_write(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_DATA, val); + + regmap_read(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL, ®); + reg |= TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL_ADDR_INIT_EN; + + regmap_write(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL, reg); + + regmap_read(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL, ®); + reg |= TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL_RW_WRITE; + + regmap_write(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL, reg); +} + +static void tegra210_adx_update_map_ram(struct tegra210_adx *adx) +{ + int i; + + for (i = 0; i < TEGRA210_ADX_RAM_DEPTH; i++) + tegra210_adx_write_map_ram(adx, i, adx->map[i]); +} + +#ifdef TEGRA210_ADX_MAP_READ +static unsigned int tegra210_adx_read_map_ram(struct tegra210_adx *adx, + unsigned int addr) +{ + unsigned int val, wait; + wait = 0xffff; + + regmap_write(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL, + (addr << TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL_RAM_ADDR_SHIFT)); + + regmap_read(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL, &val); + val |= TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL_ADDR_INIT_EN; + regmap_write(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL, val); + regmap_read(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL, &val); + val &= ~(TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL_RW_WRITE); + regmap_write(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL, val); + + do { + regmap_read(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL, &val); + wait--; + if (!wait) + return -EINVAL; + } while (val & 0x80000000); + + regmap_read(adx->regmap, TEGRA210_ADX_AHUBRAMCTL_ADX_DATA, &val); + + return val; +} +#endif + +static int tegra210_adx_runtime_suspend(struct device *dev) +{ + struct tegra210_adx *adx = dev_get_drvdata(dev); + + regcache_cache_only(adx->regmap, true); + +#ifndef CONFIG_MACH_GRENADA + clk_disable_unprepare(adx->clk_adx); +#endif + + return 0; +} + +static int tegra210_adx_runtime_resume(struct device *dev) +{ + struct tegra210_adx *adx = dev_get_drvdata(dev); +#ifndef CONFIG_MACH_GRENADA + int ret; + + ret = clk_prepare_enable(adx->clk_adx); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } +#endif + + regcache_cache_only(adx->regmap, false); + + return 0; +} + +static int tegra210_adx_set_audio_cif(struct tegra210_adx *adx, + 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; + + adx->soc_data->set_audio_cif(adx->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra210_adx_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_adx *adx = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = tegra210_adx_set_audio_cif(adx, params, + TEGRA210_ADX_AXBAR_TX1_CIF_CTRL + + (dai->id * TEGRA210_ADX_AUDIOCIF_CH_STRIDE)); + + return ret; +} + +static int tegra210_adx_out_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + struct tegra210_adx *adx = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + tegra210_adx_enable_outstream(adx, dai->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + tegra210_adx_disable_outstream(adx, dai->id); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra210_adx_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_adx *adx = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = tegra210_adx_set_audio_cif(adx, params, + TEGRA210_ADX_AXBAR_RX_CIF_CTRL); + return ret; +} + +int tegra210_adx_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + struct device *dev = dai->dev; + struct tegra210_adx *adx = snd_soc_dai_get_drvdata(dai); + unsigned int byte_mask1 = 0, byte_mask2 = 0; + unsigned int out_stream_idx, out_ch_idx, out_byte_idx; + int i; + + if ((rx_num < 1) || (rx_num > 64)) { + dev_err(dev, "Doesn't support %d rx_num, need to be 1 to 64\n", + rx_num); + return -EINVAL; + } + + if (!rx_slot) { + dev_err(dev, "rx_slot is NULL\n"); + return -EINVAL; + } + + memset(adx->map, 0, sizeof(adx->map)); + for (i = 0; i < rx_num; i++) { + if (rx_slot[i] != 0) { + /* getting mapping information */ + /* n-th output stream : 0 to 3 */ + out_stream_idx = (rx_slot[i] >> 16) & 0x3; + /* n-th audio channel of output stream : 1 to 16 */ + out_ch_idx = (rx_slot[i] >> 8) & 0x1f; + /* n-th byte of audio channel : 0 to 3 */ + out_byte_idx = rx_slot[i] & 0x3; + tegra210_adx_set_map_table(adx, i, out_stream_idx, + out_ch_idx - 1, + out_byte_idx); + + /* making byte_mask */ + if (i > 32) + byte_mask2 |= 1 << (32 - i); + else + byte_mask1 |= 1 << i; + } + } + + tegra210_adx_update_map_ram(adx); + tegra210_adx_set_in_byte_mask(adx, byte_mask1, byte_mask2); + + return 0; +} + +static int tegra210_adx_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra210_adx *adx = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = adx->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_adx_in_dai_ops = { + .hw_params = tegra210_adx_in_hw_params, + .set_channel_map = tegra210_adx_set_channel_map, +}; + +static struct snd_soc_dai_ops tegra210_adx_out_dai_ops = { + .hw_params = tegra210_adx_out_hw_params, + .trigger = tegra210_adx_out_trigger, +}; + +#define OUT_DAI(id) \ + { \ + .name = "OUT" #id, \ + .capture = { \ + .stream_name = "OUT" #id " Transmit", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .ops = &tegra210_adx_out_dai_ops, \ + } + +#define IN_DAI(sname, dai_ops) \ + { \ + .name = #sname, \ + .playback = { \ + .stream_name = #sname " Receive", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .ops = dai_ops, \ + } + +static struct snd_soc_dai_driver tegra210_adx_dais[] = { + OUT_DAI(1), + OUT_DAI(2), + OUT_DAI(3), + OUT_DAI(4), + IN_DAI(IN, &tegra210_adx_in_dai_ops), +}; + +static const struct snd_soc_dapm_widget tegra210_adx_widgets[] = { + SND_SOC_DAPM_AIF_IN("IN", NULL, 0, TEGRA210_ADX_ENABLE, 0, 0), + SND_SOC_DAPM_AIF_OUT("OUT1", NULL, 0, TEGRA210_ADX_CTRL, 0, 0), + SND_SOC_DAPM_AIF_OUT("OUT2", NULL, 0, TEGRA210_ADX_CTRL, 1, 0), + SND_SOC_DAPM_AIF_OUT("OUT3", NULL, 0, TEGRA210_ADX_CTRL, 2, 0), + SND_SOC_DAPM_AIF_OUT("OUT4", NULL, 0, TEGRA210_ADX_CTRL, 3, 0), +}; + +static const struct snd_soc_dapm_route tegra210_adx_routes[] = { + { "IN", NULL, "IN Receive" }, + { "OUT1", NULL, "IN" }, + { "OUT2", NULL, "IN" }, + { "OUT3", NULL, "IN" }, + { "OUT4", NULL, "IN" }, + { "OUT1 Transmit", NULL, "OUT1" }, + { "OUT2 Transmit", NULL, "OUT2" }, + { "OUT3 Transmit", NULL, "OUT3" }, + { "OUT4 Transmit", NULL, "OUT4" }, +}; + +static struct snd_soc_codec_driver tegra210_adx_codec = { + .probe = tegra210_adx_codec_probe, + .dapm_widgets = tegra210_adx_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_adx_widgets), + .dapm_routes = tegra210_adx_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_adx_routes), +}; + +static bool tegra210_adx_wr_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA210_ADX_AXBAR_TX_INT_MASK: + case TEGRA210_ADX_AXBAR_TX_INT_SET: + case TEGRA210_ADX_AXBAR_TX_INT_CLEAR: + case TEGRA210_ADX_AXBAR_TX1_CIF_CTRL: + case TEGRA210_ADX_AXBAR_TX2_CIF_CTRL: + case TEGRA210_ADX_AXBAR_TX3_CIF_CTRL: + case TEGRA210_ADX_AXBAR_TX4_CIF_CTRL: + case TEGRA210_ADX_AXBAR_RX_INT_MASK: + case TEGRA210_ADX_AXBAR_RX_INT_SET: + case TEGRA210_ADX_AXBAR_RX_INT_CLEAR: + case TEGRA210_ADX_AXBAR_RX_CIF_CTRL: + case TEGRA210_ADX_ENABLE: + case TEGRA210_ADX_SOFT_RESET: + case TEGRA210_ADX_CG: + case TEGRA210_ADX_CTRL: + case TEGRA210_ADX_IN_BYTE_EN0: + case TEGRA210_ADX_IN_BYTE_EN1: + case TEGRA210_ADX_CYA: + case TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL: + case TEGRA210_ADX_AHUBRAMCTL_ADX_DATA: + return true; + default: + return false; + }; +} + +static bool tegra210_adx_rd_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA210_ADX_AXBAR_RX_STATUS: + case TEGRA210_ADX_AXBAR_RX_INT_STATUS: + case TEGRA210_ADX_AXBAR_RX_INT_MASK: + case TEGRA210_ADX_AXBAR_RX_INT_SET: + case TEGRA210_ADX_AXBAR_RX_INT_CLEAR: + case TEGRA210_ADX_AXBAR_RX_CIF_CTRL: + case TEGRA210_ADX_AXBAR_TX_STATUS: + case TEGRA210_ADX_AXBAR_TX_INT_STATUS: + case TEGRA210_ADX_AXBAR_TX_INT_MASK: + case TEGRA210_ADX_AXBAR_TX_INT_SET: + case TEGRA210_ADX_AXBAR_TX_INT_CLEAR: + case TEGRA210_ADX_AXBAR_TX1_CIF_CTRL: + case TEGRA210_ADX_AXBAR_TX2_CIF_CTRL: + case TEGRA210_ADX_AXBAR_TX3_CIF_CTRL: + case TEGRA210_ADX_AXBAR_TX4_CIF_CTRL: + case TEGRA210_ADX_ENABLE: + case TEGRA210_ADX_SOFT_RESET: + case TEGRA210_ADX_CG: + case TEGRA210_ADX_CTRL: + case TEGRA210_ADX_IN_BYTE_EN0: + case TEGRA210_ADX_IN_BYTE_EN1: + case TEGRA210_ADX_CYA: + case TEGRA210_ADX_AHUBRAMCTL_ADX_CTRL: + case TEGRA210_ADX_AHUBRAMCTL_ADX_DATA: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra210_adx_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_ADX_AHUBRAMCTL_ADX_DATA, + .writeable_reg = tegra210_adx_wr_reg, + .readable_reg = tegra210_adx_rd_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra210_adx_soc_data soc_data_tegra210 = { + .set_audio_cif = tegra210_xbar_set_cif +}; + +static const struct of_device_id tegra210_adx_of_match[] = { + { .compatible = "nvidia,tegra210-adx", .data = &soc_data_tegra210 }, + {}, +}; + +static int tegra210_adx_platform_probe(struct platform_device *pdev) +{ + struct tegra210_adx *adx; + struct resource *mem, *memregion; + void __iomem *regs; + int ret = 0; + const struct of_device_id *match; + struct tegra210_adx_soc_data *soc_data; + + match = of_match_device(tegra210_adx_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra210_adx_soc_data *)match->data; + + adx = devm_kzalloc(&pdev->dev, sizeof(struct tegra210_adx), GFP_KERNEL); + if (!adx) { + dev_err(&pdev->dev, "Can't allocate tegra210_adx\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, adx); + + adx->soc_data = soc_data; + +#ifndef CONFIG_MACH_GRENADA + adx->clk_adx = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(adx->clk_adx)) { + dev_err(&pdev->dev, "Can't retrieve adx clock\n"); + ret = PTR_ERR(adx->clk_adx); + 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; + } + + memregion = devm_request_mem_region(&pdev->dev, mem->start, + resource_size(mem), DRV_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; + } + + adx->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra210_adx_regmap_config); + if (IS_ERR(adx->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(adx->regmap); + goto err_clk_put; + } + regcache_cache_only(adx->regmap, true); + + if (of_property_read_u32(pdev->dev.of_node, + "nvidia,ahub-adx-id", + &pdev->dev.id) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,ahub-adx-id\n"); + ret = -ENODEV; + goto err_clk_put; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_adx_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra210_adx_codec, + tegra210_adx_dais, + ARRAY_SIZE(tegra210_adx_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_adx_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, adx->clk_adx); +#else + dev_err(&pdev->dev, "err\n"); +#endif +err: + return ret; +} + +static int tegra210_adx_platform_remove(struct platform_device *pdev) +{ +#ifndef CONFIG_MACH_GRENADA + struct tegra210_adx *adx = dev_get_drvdata(&pdev->dev); +#endif + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_adx_runtime_suspend(&pdev->dev); + +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, adx->clk_adx); +#endif + return 0; +} + +static const struct dev_pm_ops tegra210_adx_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_adx_runtime_suspend, + tegra210_adx_runtime_resume, NULL) +}; + +static struct platform_driver tegra210_adx_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_adx_of_match, + .pm = &tegra210_adx_pm_ops, + }, + .probe = tegra210_adx_platform_probe, + .remove = tegra210_adx_platform_remove, +}; +module_platform_driver(tegra210_adx_driver); + +MODULE_AUTHOR("Arun Shamanna Lakshmi "); +MODULE_DESCRIPTION("Tegra210 ADX ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra210_adx_of_match); diff --git a/sound/soc/tegra-alt/tegra210_afc_alt.c b/sound/soc/tegra-alt/tegra210_afc_alt.c new file mode 100644 index 00000000..198b7a02 --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_afc_alt.c @@ -0,0 +1,485 @@ +/* + * tegra210_afc_alt.c - Tegra210 AFC 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_afc_alt.h" + +#define DRV_NAME "tegra210-afc" + +static int tegra210_afc_runtime_suspend(struct device *dev) +{ + struct tegra210_afc *afc = dev_get_drvdata(dev); + + regcache_cache_only(afc->regmap, true); + +#ifndef CONFIG_MACH_GRENADA + clk_disable_unprepare(afc->clk_afc); +#endif + + return 0; +} + +static int tegra210_afc_runtime_resume(struct device *dev) +{ + struct tegra210_afc *afc = dev_get_drvdata(dev); + +#ifndef CONFIG_MACH_GRENADA + int ret; + + ret = clk_prepare_enable(afc->clk_afc); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } +#endif + + regcache_cache_only(afc->regmap, false); + + return 0; +} + +/* returns the destination I2S id connected along the AFC path */ +static unsigned int tegra210_afc_get_i2s_id(unsigned int afc_id) +{ + unsigned int i2s_reg, i2s_val, amx_reg, amx_val, i, j; + + for (i = 1; i < 6; i++) { + i2s_val = 0; + i2s_reg = TEGRA210_XBAR_PART1_RX + + TEGRA210_XBAR_RX_STRIDE * (i + 0xF); + tegra210_xbar_read_reg(i2s_reg, &i2s_val); + if ((i2s_val >> 24) & (1 << afc_id)) { + return i; + } else if (i2s_val & 0x300) { + for (j = 1; j < 9; j++) { + amx_val = 0; + amx_reg = TEGRA210_XBAR_PART1_RX + + TEGRA210_XBAR_RX_STRIDE * (j + 0x4F); + tegra210_xbar_read_reg(amx_reg, &amx_val); + if ((amx_val >> 24) & (1 << afc_id)) + return i; + } + } + } + return 1; +} + +/* returns the id if SFC is connected along the AFC src path */ +static unsigned int tegra210_afc_get_sfc_id(unsigned int afc_id) +{ + unsigned int reg, val = 0; + + reg = TEGRA210_XBAR_PART0_RX + + TEGRA210_XBAR_RX_STRIDE * (afc_id + 0x34); + + tegra210_xbar_read_reg(reg, &val); + val = val >> 24; + + return val; +} + +static int tegra210_afc_set_thresholds(struct tegra210_afc *afc, + unsigned int afc_id) +{ + unsigned int i2s_id, value; + + if (tegra210_afc_get_sfc_id(afc_id)) { + /* TODO program thresholds using SRC_BURST */ + } else { + value = 8 << TEGRA210_AFC_FIFO_HIGH_THRESHOLD_SHIFT; + value |= 7 << TEGRA210_AFC_FIFO_START_THRESHOLD_SHIFT; + value |= 6; + } + regmap_write(afc->regmap, TEGRA210_AFC_TXCIF_FIFO_PARAMS, value); + + i2s_id = tegra210_afc_get_i2s_id(afc_id); + + value |= i2s_id << TEGRA210_AFC_DEST_I2S_ID_SHIFT; + + regmap_write(afc->regmap, TEGRA210_AFC_DEST_I2S_PARAMS, value); + + return 0; +} + +static int tegra210_afc_set_audio_cif(struct tegra210_afc *afc, + 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; + + afc->soc_data->set_audio_cif(afc->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra210_afc_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_afc *afc = snd_soc_dai_get_drvdata(dai); + int ret; + + /* set RX cif and TX cif */ + ret = tegra210_afc_set_audio_cif(afc, params, + TEGRA210_AFC_AXBAR_RX_CIF_CTRL); + if (ret) { + dev_err(dev, "Can't set AFC RX CIF: %d\n", ret); + return ret; + } + ret = tegra210_afc_set_audio_cif(afc, params, + TEGRA210_AFC_AXBAR_TX_CIF_CTRL); + if (ret) { + dev_err(dev, "Can't set AFC TX CIF: %d\n", ret); + return ret; + } + + /* program the thresholds, destn i2s id, PPM values */ + tegra210_afc_set_thresholds(afc, dev->id); + + return ret; + +} + +static int tegra210_afc_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra210_afc *afc = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = afc->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_afc_dai_ops = { + .hw_params = tegra210_afc_hw_params, +}; + +static struct snd_soc_dai_driver tegra210_afc_dais[] = { + { + .name = "AFC IN", + .playback = { + .stream_name = "AFC Receive", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "AFC OUT", + .capture = { + .stream_name = "AFC Transmit", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra210_afc_dai_ops, + } +}; + +static const struct snd_soc_dapm_widget tegra210_afc_widgets[] = { + SND_SOC_DAPM_AIF_IN("AFC RX", NULL, 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_OUT("AFC TX", NULL, 0, TEGRA210_AFC_ENABLE, + TEGRA210_AFC_EN_SHIFT, 0), +}; + +static const struct snd_soc_dapm_route tegra210_afc_routes[] = { + { "AFC RX", NULL, "AFC Receive" }, + { "AFC TX", NULL, "AFC RX" }, + { "AFC Transmit", NULL, "AFC TX" }, +}; + +static struct snd_soc_codec_driver tegra210_afc_codec = { + .probe = tegra210_afc_codec_probe, + .dapm_widgets = tegra210_afc_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_afc_widgets), + .dapm_routes = tegra210_afc_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_afc_routes), +}; + +static bool tegra210_afc_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_AFC_AXBAR_RX_STATUS: + case TEGRA210_AFC_AXBAR_RX_CIF_CTRL: + case TEGRA210_AFC_AXBAR_RX_CYA: + case TEGRA210_AFC_AXBAR_TX_STATUS: + case TEGRA210_AFC_AXBAR_TX_INT_STATUS: + case TEGRA210_AFC_AXBAR_TX_INT_MASK: + case TEGRA210_AFC_AXBAR_TX_INT_SET: + case TEGRA210_AFC_AXBAR_TX_INT_CLEAR: + case TEGRA210_AFC_AXBAR_TX_CIF_CTRL: + case TEGRA210_AFC_AXBAR_TX_CYA: + case TEGRA210_AFC_ENABLE: + case TEGRA210_AFC_SOFT_RESET: + case TEGRA210_AFC_CG: + case TEGRA210_AFC_STATUS: + case TEGRA210_AFC_INT_STATUS: + case TEGRA210_AFC_INT_MASK: + case TEGRA210_AFC_INT_SET: + case TEGRA210_AFC_INT_CLEAR: + case TEGRA210_AFC_DEST_I2S_PARAMS: + case TEGRA210_AFC_TXCIF_FIFO_PARAMS: + case TEGRA210_AFC_CLK_PPM_DIFF: + case TEGRA210_AFC_DBG_CTRL: + case TEGRA210_AFC_TOTAL_SAMPLES: + case TEGRA210_AFC_DECIMATION_SAMPLES: + case TEGRA210_AFC_INTERPOLATION_SAMPLES: + case TEGRA210_AFC_DBG_INTERNAL: + case TEGRA210_AFC_LCOEF_1_4_0: + case TEGRA210_AFC_LCOEF_1_4_1: + case TEGRA210_AFC_LCOEF_1_4_2: + case TEGRA210_AFC_LCOEF_1_4_3: + case TEGRA210_AFC_LCOEF_1_4_4: + case TEGRA210_AFC_LCOEF_1_4_5: + case TEGRA210_AFC_LCOEF_2_4_0: + case TEGRA210_AFC_LCOEF_2_4_1: + case TEGRA210_AFC_LCOEF_2_4_2: + case TEGRA210_AFC_CYA: + return true; + default: + return false; + }; +} + +static bool tegra210_afc_volatile_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static const struct regmap_config tegra210_afc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_AFC_CYA, + .writeable_reg = tegra210_afc_wr_rd_reg, + .readable_reg = tegra210_afc_wr_rd_reg, + .volatile_reg = tegra210_afc_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra210_afc_soc_data soc_data_tegra210 = { + .set_audio_cif = tegra210_xbar_set_cif, +}; + +static const struct of_device_id tegra210_afc_of_match[] = { + { .compatible = "nvidia,tegra210-afc", .data = &soc_data_tegra210 }, + {}, +}; + +static int tegra210_afc_platform_probe(struct platform_device *pdev) +{ + struct tegra210_afc *afc; + struct resource *mem, *memregion; + void __iomem *regs; + int ret = 0; + const struct of_device_id *match; + struct tegra210_afc_soc_data *soc_data; + + match = of_match_device(tegra210_afc_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra210_afc_soc_data *)match->data; + + afc = devm_kzalloc(&pdev->dev, sizeof(struct tegra210_afc), GFP_KERNEL); + if (!afc) { + dev_err(&pdev->dev, "Can't allocate afc\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, afc); + + afc->soc_data = soc_data; + + /* initialize default destination I2S */ + afc->destination_i2s = 1; + +#ifndef CONFIG_MACH_GRENADA + afc->clk_afc = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(afc->clk_afc)) { + dev_err(&pdev->dev, "Can't retrieve afc clock\n"); + ret = PTR_ERR(afc->clk_afc); + 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; + } + + afc->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra210_afc_regmap_config); + if (IS_ERR(afc->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(afc->regmap); + goto err_clk_put; + } + regcache_cache_only(afc->regmap, true); + + if (of_property_read_u32(pdev->dev.of_node, + "nvidia,ahub-afc-id", + &pdev->dev.id) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,ahub-afc-id\n"); + ret = -ENODEV; + goto err_clk_put; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_afc_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra210_afc_codec, + tegra210_afc_dais, + ARRAY_SIZE(tegra210_afc_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_afc_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, afc->clk_afc); +#else + dev_err(&pdev->dev, "error\n"); +#endif +err: + return ret; +} + +static int tegra210_afc_platform_remove(struct platform_device *pdev) +{ +#ifndef CONFIG_MACH_GRENADA + struct tegra210_afc *afc = dev_get_drvdata(&pdev->dev); +#endif + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_afc_runtime_suspend(&pdev->dev); + +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, afc->clk_afc); +#endif + + return 0; +} + +static const struct dev_pm_ops tegra210_afc_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_afc_runtime_suspend, + tegra210_afc_runtime_resume, NULL) +}; + +static struct platform_driver tegra210_afc_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_afc_of_match, + .pm = &tegra210_afc_pm_ops, + }, + .probe = tegra210_afc_platform_probe, + .remove = tegra210_afc_platform_remove, +}; +module_platform_driver(tegra210_afc_driver) + +MODULE_AUTHOR("Arun Shamanna Lakshmi "); +MODULE_DESCRIPTION("Tegra210 AFC ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra210_afc_of_match); diff --git a/sound/soc/tegra-alt/tegra210_amx_alt.c b/sound/soc/tegra-alt/tegra210_amx_alt.c new file mode 100644 index 00000000..8f9632ce --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_amx_alt.c @@ -0,0 +1,696 @@ +/* + * tegra210_amx_alt.c - Tegra210 AMX 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 "tegra210_xbar_alt.h" +#include "tegra210_amx_alt.h" + +#define DRV_NAME "tegra210_amx" + +/** + * tegra210_amx_set_master_stream - set master stream and dependency + * @amx: struct of tegra210_amx + * @stream_id: one of input stream id to be a master + * @dependency: master dependency for tansferring + * 0 - wait on all, 1 - wait on any + * + * This dependency matter on starting point not every frame. + * Once amx starts to run, it is work as wait on all. + */ +static void tegra210_amx_set_master_stream(struct tegra210_amx *amx, + unsigned int stream_id, + unsigned int dependency) +{ + unsigned int mask, val; + + mask = (TEGRA210_AMX_CTRL_MSTR_RX_NUM_MASK | + TEGRA210_AMX_CTRL_RX_DEP_MASK); + + val = (stream_id << TEGRA210_AMX_CTRL_MSTR_RX_NUN_SHIFT) | + (dependency << TEGRA210_AMX_CTRL_RX_DEP_SHIFT); + + regmap_update_bits(amx->regmap, TEGRA210_AMX_CTRL, mask, val); +} + +/** + * tegra210_amx_enable_instream - enable input stream + * @amx: struct of tegra210_amx + * @stream_id: amx input stream id for enabling + */ +static void tegra210_amx_enable_instream(struct tegra210_amx *amx, + unsigned int stream_id) +{ + int reg; + + reg = TEGRA210_AMX_CTRL; + + regmap_update_bits(amx->regmap, reg, + TEGRA210_AMX_RX_ENABLE << stream_id, + TEGRA210_AMX_RX_ENABLE << stream_id); +} + +/** + * tegra210_amx_disable_instream - disable input stream + * @amx: struct of tegra210_amx + * @stream_id: amx input stream id for disabling + */ +static void tegra210_amx_disable_instream(struct tegra210_amx *amx, + unsigned int stream_id) +{ + int reg; + + reg = TEGRA210_AMX_CTRL; + + regmap_update_bits(amx->regmap, reg, + TEGRA210_AMX_RX_ENABLE << stream_id, + TEGRA210_AMX_RX_DISABLE); +} + +/** + * tegra210_amx_set_out_byte_mask - set byte mask for output frame + * @amx: struct of tegra210_amx + * @mask1: enable for bytes 31 ~ 0 + * @mask2: enable for bytes 63 ~ 32 + */ +static void tegra210_amx_set_out_byte_mask(struct tegra210_amx *amx, + unsigned int mask1, + unsigned int mask2) +{ + regmap_write(amx->regmap, TEGRA210_AMX_OUT_BYTE_EN0, mask1); + regmap_write(amx->regmap, TEGRA210_AMX_OUT_BYTE_EN1, mask2); +} + +/** + * tegra210_amx_set_map_table - set map table not RAM + * @amx: struct of tegra210_amx + * @out_byte_addr: byte address in one frame + * @stream_id: input stream id + * @nth_word: n-th word in the input stream + * @nth_byte: n-th byte in the word + */ +static void tegra210_amx_set_map_table(struct tegra210_amx *amx, + unsigned int out_byte_addr, + unsigned int stream_id, + unsigned int nth_word, + unsigned int nth_byte) +{ + unsigned char *bytes_map = (unsigned char *)&amx->map; + + bytes_map[out_byte_addr] = + (stream_id << TEGRA210_AMX_MAP_STREAM_NUMBER_SHIFT) | + (nth_word << TEGRA210_AMX_MAP_WORD_NUMBER_SHIFT) | + (nth_byte << TEGRA210_AMX_MAP_BYTE_NUMBER_SHIFT); +} + +/** + * tegra210_amx_write_map_ram - write map information in RAM + * @amx: struct of tegra210_amx + * @addr: n-th word of input stream + * @val : bytes mapping information of the word + */ +static void tegra210_amx_write_map_ram(struct tegra210_amx *amx, + unsigned int addr, + unsigned int val) +{ + unsigned int reg; + + regmap_write(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL, + (addr << TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL_RAM_ADDR_SHIFT)); + + regmap_write(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_DATA, val); + + regmap_read(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL, ®); + reg |= TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL_ADDR_INIT_EN; + + regmap_write(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL, reg); + + regmap_read(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL, ®); + reg |= TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL_RW_WRITE; + + regmap_write(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL, reg); +} + +static void tegra210_amx_update_map_ram(struct tegra210_amx *amx) +{ + int i; + + for (i = 0; i < TEGRA210_AMX_RAM_DEPTH; i++) + tegra210_amx_write_map_ram(amx, i, amx->map[i]); +} + +static int tegra210_amx_runtime_suspend(struct device *dev) +{ + struct tegra210_amx *amx = dev_get_drvdata(dev); + + regcache_cache_only(amx->regmap, true); + +#ifndef CONFIG_MACH_GRENADA + clk_disable_unprepare(amx->clk_amx); +#endif + + return 0; +} + +#ifdef TEGRA210_AMX_MAP_READ +static unsigned int tegra210_amx_read_map_ram(struct tegra210_amx *amx, + unsigned int addr) +{ + unsigned int val, wait; + wait = 0xffff; + + regmap_write(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL, + (addr << TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL_RAM_ADDR_SHIFT)); + + regmap_read(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL, &val); + val |= TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL_ADDR_INIT_EN; + regmap_write(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL, val); + regmap_read(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL, &val); + val &= ~(TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL_RW_WRITE); + regmap_write(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL, val); + + do { + regmap_read(amx->regmap, + TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL, &val); + wait--; + if (!wait) + return -EINVAL; + } while (val & 0x80000000); + + regmap_read(amx->regmap, TEGRA210_AMX_AHUBRAMCTL_AMX_DATA, &val); + + return val; +} +#endif + +static int tegra210_amx_runtime_resume(struct device *dev) +{ + struct tegra210_amx *amx = dev_get_drvdata(dev); +#ifndef CONFIG_MACH_GRENADA + int ret; + + ret = clk_prepare_enable(amx->clk_amx); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } +#endif + + regcache_cache_only(amx->regmap, false); + + return 0; +} + +static int tegra210_amx_set_audio_cif(struct tegra210_amx *amx, + 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; + + amx->soc_data->set_audio_cif(amx->regmap, reg, &cif_conf); + + return 0; +} + + +static int tegra210_amx_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = tegra210_amx_set_audio_cif(amx, params, + TEGRA210_AMX_AXBAR_RX1_CIF_CTRL + + (dai->id * TEGRA210_AMX_AUDIOCIF_CH_STRIDE)); + + return ret; +} + +static int tegra210_amx_in_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + tegra210_amx_enable_instream(amx, dai->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + tegra210_amx_disable_instream(amx, dai->id); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra210_amx_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = tegra210_amx_set_audio_cif(amx, params, + TEGRA210_AMX_AXBAR_TX_CIF_CTRL); + + return ret; +} + +int tegra210_amx_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + struct device *dev = dai->dev; + struct tegra210_amx *amx = snd_soc_dai_get_drvdata(dai); + unsigned int byte_mask1 = 0, byte_mask2 = 0; + unsigned int in_stream_idx, in_ch_idx, in_byte_idx; + int i; + + if ((tx_num < 1) || (tx_num > 64)) { + dev_err(dev, "Doesn't support %d tx_num, need to be 1 to 64\n", + tx_num); + return -EINVAL; + } + + if (!tx_slot) { + dev_err(dev, "tx_slot is NULL\n"); + return -EINVAL; + } + + tegra210_amx_set_master_stream(amx, 0, + TEGRA210_AMX_WAIT_ON_ALL); + + memset(amx->map, 0, sizeof(amx->map)); + + for (i = 0; i < tx_num; i++) { + if (tx_slot[i] != 0) { + /* getting mapping information */ + /* n-th input stream : 0 to 3 */ + in_stream_idx = (tx_slot[i] >> 16) & 0x3; + /* n-th audio channel of input stream : 1 to 16 */ + in_ch_idx = (tx_slot[i] >> 8) & 0x1f; + /* n-th byte of audio channel : 0 to 3 */ + in_byte_idx = tx_slot[i] & 0x3; + tegra210_amx_set_map_table(amx, i, in_stream_idx, + in_ch_idx - 1, + in_byte_idx); + + /* making byte_mask */ + if (i > 32) + byte_mask2 |= 1 << (32 - i); + else + byte_mask1 |= 1 << i; + } + } + + tegra210_amx_update_map_ram(amx); + tegra210_amx_set_out_byte_mask(amx, byte_mask1, byte_mask2); + + return 0; +} + +static int tegra210_amx_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra210_amx *amx = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = amx->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_amx_out_dai_ops = { + .hw_params = tegra210_amx_out_hw_params, + .set_channel_map = tegra210_amx_set_channel_map, +}; + +static struct snd_soc_dai_ops tegra210_amx_in_dai_ops = { + .hw_params = tegra210_amx_in_hw_params, + .trigger = tegra210_amx_in_trigger, +}; + +#define IN_DAI(id) \ + { \ + .name = "IN" #id, \ + .playback = { \ + .stream_name = "IN" #id " Receive", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .ops = &tegra210_amx_in_dai_ops, \ + } + +#define OUT_DAI(sname, dai_ops) \ + { \ + .name = #sname, \ + .capture = { \ + .stream_name = #sname " Transmit", \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .ops = dai_ops, \ + } + +static struct snd_soc_dai_driver tegra210_amx_dais[] = { + IN_DAI(1), + IN_DAI(2), + IN_DAI(3), + IN_DAI(4), + OUT_DAI(OUT, &tegra210_amx_out_dai_ops), +}; + +static const struct snd_soc_dapm_widget tegra210_amx_widgets[] = { + SND_SOC_DAPM_AIF_IN("IN1", NULL, 0, TEGRA210_AMX_CTRL, 0, 0), + SND_SOC_DAPM_AIF_IN("IN2", NULL, 0, TEGRA210_AMX_CTRL, 1, 0), + SND_SOC_DAPM_AIF_IN("IN3", NULL, 0, TEGRA210_AMX_CTRL, 2, 0), + SND_SOC_DAPM_AIF_IN("IN4", NULL, 0, TEGRA210_AMX_CTRL, 3, 0), + SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, TEGRA210_AMX_ENABLE, 0, 0), +}; + +static const struct snd_soc_dapm_route tegra210_amx_routes[] = { + { "IN1", NULL, "IN1 Receive" }, + { "IN2", NULL, "IN2 Receive" }, + { "IN3", NULL, "IN3 Receive" }, + { "IN4", NULL, "IN4 Receive" }, + { "OUT", NULL, "IN1" }, + { "OUT", NULL, "IN2" }, + { "OUT", NULL, "IN3" }, + { "OUT", NULL, "IN4" }, + { "OUT Transmit", NULL, "OUT" }, +}; + +static struct snd_soc_codec_driver tegra210_amx_codec = { + .probe = tegra210_amx_codec_probe, + .dapm_widgets = tegra210_amx_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_amx_widgets), + .dapm_routes = tegra210_amx_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_amx_routes), +}; + +static bool tegra210_amx_wr_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA210_AMX_AXBAR_RX_INT_MASK: + case TEGRA210_AMX_AXBAR_RX_INT_SET: + case TEGRA210_AMX_AXBAR_RX_INT_CLEAR: + case TEGRA210_AMX_AXBAR_RX1_CIF_CTRL: + case TEGRA210_AMX_AXBAR_RX2_CIF_CTRL: + case TEGRA210_AMX_AXBAR_RX3_CIF_CTRL: + case TEGRA210_AMX_AXBAR_RX4_CIF_CTRL: + case TEGRA210_AMX_AXBAR_TX_INT_MASK: + case TEGRA210_AMX_AXBAR_TX_INT_SET: + case TEGRA210_AMX_AXBAR_TX_INT_CLEAR: + case TEGRA210_AMX_AXBAR_TX_CIF_CTRL: + case TEGRA210_AMX_ENABLE: + case TEGRA210_AMX_SOFT_RESET: + case TEGRA210_AMX_CG: + case TEGRA210_AMX_CTRL: + case TEGRA210_AMX_OUT_BYTE_EN0: + case TEGRA210_AMX_OUT_BYTE_EN1: + case TEGRA210_AMX_CYA: + case TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL: + case TEGRA210_AMX_AHUBRAMCTL_AMX_DATA: + return true; + default: + return false; + }; +} + +static bool tegra210_amx_rd_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA210_AMX_AXBAR_RX_STATUS: + case TEGRA210_AMX_AXBAR_RX_INT_STATUS: + case TEGRA210_AMX_AXBAR_RX_INT_MASK: + case TEGRA210_AMX_AXBAR_RX_INT_SET: + case TEGRA210_AMX_AXBAR_RX_INT_CLEAR: + case TEGRA210_AMX_AXBAR_RX1_CIF_CTRL: + case TEGRA210_AMX_AXBAR_RX2_CIF_CTRL: + case TEGRA210_AMX_AXBAR_RX3_CIF_CTRL: + case TEGRA210_AMX_AXBAR_RX4_CIF_CTRL: + case TEGRA210_AMX_AXBAR_TX_STATUS: + case TEGRA210_AMX_AXBAR_TX_INT_STATUS: + case TEGRA210_AMX_AXBAR_TX_INT_MASK: + case TEGRA210_AMX_AXBAR_TX_INT_SET: + case TEGRA210_AMX_AXBAR_TX_INT_CLEAR: + case TEGRA210_AMX_AXBAR_TX_CIF_CTRL: + case TEGRA210_AMX_ENABLE: + case TEGRA210_AMX_SOFT_RESET: + case TEGRA210_AMX_CG: + case TEGRA210_AMX_STATUS: + case TEGRA210_AMX_INT_STATUS: + case TEGRA210_AMX_CTRL: + case TEGRA210_AMX_OUT_BYTE_EN0: + case TEGRA210_AMX_OUT_BYTE_EN1: + case TEGRA210_AMX_CYA: + case TEGRA210_AMX_DBG: + case TEGRA210_AMX_AHUBRAMCTL_AMX_CTRL: + case TEGRA210_AMX_AHUBRAMCTL_AMX_DATA: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra210_amx_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_AMX_AHUBRAMCTL_AMX_DATA, + .writeable_reg = tegra210_amx_wr_reg, + .readable_reg = tegra210_amx_rd_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra210_amx_soc_data soc_data_tegra210 = { + .set_audio_cif = tegra210_xbar_set_cif +}; + +static const struct of_device_id tegra210_amx_of_match[] = { + { .compatible = "nvidia,tegra210-amx", .data = &soc_data_tegra210 }, + {}, +}; + +static int tegra210_amx_platform_probe(struct platform_device *pdev) +{ + struct tegra210_amx *amx; + struct resource *mem, *memregion; + void __iomem *regs; + int ret; + const struct of_device_id *match; + struct tegra210_amx_soc_data *soc_data; + + match = of_match_device(tegra210_amx_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra210_amx_soc_data *)match->data; + + amx = devm_kzalloc(&pdev->dev, sizeof(struct tegra210_amx), GFP_KERNEL); + if (!amx) { + dev_err(&pdev->dev, "Can't allocate tegra210_amx\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, amx); + + amx->soc_data = soc_data; + +#ifndef CONFIG_MACH_GRENADA + amx->clk_amx = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(amx->clk_amx)) { + dev_err(&pdev->dev, "Can't retrieve tegra210_amx clock\n"); + ret = PTR_ERR(amx->clk_amx); + 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), DRV_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; + } + + amx->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra210_amx_regmap_config); + if (IS_ERR(amx->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(amx->regmap); + goto err_clk_put; + } + regcache_cache_only(amx->regmap, true); + + if (of_property_read_u32(pdev->dev.of_node, + "nvidia,ahub-amx-id", + &pdev->dev.id) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,ahub-amx-id\n"); + ret = -ENODEV; + goto err_clk_put; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_amx_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra210_amx_codec, + tegra210_amx_dais, + ARRAY_SIZE(tegra210_amx_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_amx_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, amx->clk_amx); +#else + dev_err(&pdev->dev, "error\n"); +#endif +err: + return ret; +} + +static int tegra210_amx_platform_remove(struct platform_device *pdev) +{ +#ifndef CONFIG_MACH_GRENADA + struct tegra210_amx *amx = dev_get_drvdata(&pdev->dev); +#endif + + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_amx_runtime_suspend(&pdev->dev); + +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, amx->clk_amx); +#endif + + return 0; +} + +static const struct dev_pm_ops tegra210_amx_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_amx_runtime_suspend, + tegra210_amx_runtime_resume, NULL) +}; + +static struct platform_driver tegra210_amx_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_amx_of_match, + .pm = &tegra210_amx_pm_ops, + }, + .probe = tegra210_amx_platform_probe, + .remove = tegra210_amx_platform_remove, +}; +module_platform_driver(tegra210_amx_driver); + +MODULE_AUTHOR("Songhee Baek "); +MODULE_DESCRIPTION("Tegra210 AMX ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra210_amx_of_match); diff --git a/sound/soc/tegra-alt/tegra210_i2s_alt.c b/sound/soc/tegra-alt/tegra210_i2s_alt.c new file mode 100644 index 00000000..01199e08 --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_i2s_alt.c @@ -0,0 +1,664 @@ +/* + * tegra210_i2s.c - Tegra210 I2S 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_i2s_alt.h" + +#define DRV_NAME "tegra210-i2s" + +static int tegra210_i2s_set_clock_rate(struct device *dev, int clock_rate) +{ +#ifndef CONFIG_MACH_GRENADA + unsigned int val; + struct tegra210_i2s *i2s = dev_get_drvdata(dev); + int ret; + + regmap_read(i2s->regmap, TEGRA210_I2S_CTRL, &val); + + if ((val & TEGRA210_I2S_CTRL_MASTER_EN_MASK) == + TEGRA210_I2S_CTRL_MASTER_EN) { + ret = clk_set_parent(i2s->clk_i2s, i2s->clk_pll_a_out0); + if (ret) { + dev_err(dev, "Can't set parent of I2S clock\n"); + return ret; + } + + ret = clk_set_rate(i2s->clk_i2s, clock_rate); + if (ret) { + dev_err(dev, "Can't set I2S clock rate: %d\n", ret); + return ret; + } + } else { + ret = clk_set_rate(i2s->clk_i2s_sync, clock_rate); + if (ret) { + dev_err(dev, "Can't set I2S sync clock rate\n"); + return ret; + } + + ret = clk_set_parent(i2s->clk_i2s, i2s->clk_i2s_sync); + if (ret) { + dev_err(dev, "Can't set parent of i2s clock\n"); + return ret; + } + } + + return ret; +#else + return 0; +#endif +} + +static int tegra210_i2s_runtime_suspend(struct device *dev) +{ + struct tegra210_i2s *i2s = dev_get_drvdata(dev); + + regcache_cache_only(i2s->regmap, true); + 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 ret; + + ret = clk_prepare_enable(i2s->clk_i2s); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + regcache_cache_only(i2s->regmap, false); + + return 0; +} + +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 = TEGRA210_I2S_CTRL_EDGE_CTRL_MASK; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val = TEGRA210_I2S_CTRL_EDGE_CTRL_POS_EDGE; + break; + case SND_SOC_DAIFMT_IB_NF: + val = TEGRA210_I2S_CTRL_EDGE_CTRL_NEG_EDGE; + break; + default: + return -EINVAL; + } + + mask |= TEGRA210_I2S_CTRL_MASTER_EN_MASK; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + val &= ~(TEGRA210_I2S_CTRL_MASTER_EN); + break; + case SND_SOC_DAIFMT_CBM_CFM: + val |= TEGRA210_I2S_CTRL_MASTER_EN; + break; + default: + return -EINVAL; + } + + mask |= TEGRA210_I2S_CTRL_FRAME_FORMAT_MASK | + TEGRA210_I2S_CTRL_LRCK_POLARITY_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + val |= TEGRA210_I2S_CTRL_FRAME_FORMAT_FSYNC_MODE; + val |= TEGRA210_I2S_CTRL_LRCK_POLARITY_LOW; + break; + case SND_SOC_DAIFMT_DSP_B: + val |= TEGRA210_I2S_CTRL_FRAME_FORMAT_FSYNC_MODE; + val |= TEGRA210_I2S_CTRL_LRCK_POLARITY_HIGH; + break; + case SND_SOC_DAIFMT_I2S: + val |= TEGRA210_I2S_CTRL_FRAME_FORMAT_LRCK_MODE; + val |= TEGRA210_I2S_CTRL_LRCK_POLARITY_LOW; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= TEGRA210_I2S_CTRL_FRAME_FORMAT_LRCK_MODE; + val |= TEGRA210_I2S_CTRL_LRCK_POLARITY_LOW; + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= TEGRA210_I2S_CTRL_FRAME_FORMAT_LRCK_MODE; + val |= TEGRA210_I2S_CTRL_LRCK_POLARITY_LOW; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dai->dev); + regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL, mask, val); + /* FIXME: global enabling */ + regmap_update_bits(i2s->regmap, TEGRA210_I2S_ENABLE, + TEGRA210_I2S_EN_MASK, TEGRA210_I2S_EN); + + pm_runtime_put(dai->dev); + + return 0; +} + +static int tegra210_i2s_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct tegra210_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + i2s->srate = freq; + + 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 mask, val, reg, frame_format; + int ret, sample_size, channels, srate, i2sclock, bitcnt; + struct tegra210_xbar_cif_conf cif_conf; + + channels = params_channels(params); + if (channels < 1) { + dev_err(dev, "Doesn't support %d channels\n", channels); + return -EINVAL; + } + + mask = TEGRA210_I2S_CTRL_BIT_SIZE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val = TEGRA210_I2S_CTRL_BIT_SIZE_16; + sample_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = TEGRA210_I2S_CTRL_BIT_SIZE_24; + sample_size = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + val = TEGRA210_I2S_CTRL_BIT_SIZE_32; + sample_size = 32; + break; + default: + dev_err(dev, "Wrong format!\n"); + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL, mask, val); + + srate = i2s->srate; + + regmap_read(i2s->regmap, TEGRA210_I2S_CTRL, &val); + + frame_format = val & TEGRA210_I2S_CTRL_FRAME_FORMAT_MASK; + + if (frame_format == TEGRA210_I2S_CTRL_FRAME_FORMAT_FSYNC_MODE) { + i2sclock = srate * channels * sample_size; + regmap_write(i2s->regmap, TEGRA210_I2S_SLOT_CTRL, channels - 1); + regmap_write(i2s->regmap, TEGRA210_I2S_AXBAR_TX_SLOT_CTRL, + ((1 << channels) - 1)); + regmap_write(i2s->regmap, TEGRA210_I2S_AXBAR_RX_SLOT_CTRL, + ((1 << channels) - 1)); + } else + i2sclock = srate * channels * sample_size * 2; + + bitcnt = (i2sclock / srate) - 1; + if ((bitcnt < 0) || + (bitcnt > TEGRA210_I2S_TIMING_CHANNEL_BIT_CNT_MASK)) { + dev_err(dev, "Can't set channel bit count\n"); + return -EINVAL; + } + + ret = tegra210_i2s_set_clock_rate(dev, i2sclock); + if (ret) { + dev_err(dev, "Can't set I2S clock rate: %d\n", ret); + return ret; + } + + if ((frame_format == TEGRA210_I2S_FRAME_FORMAT_LRCK) && (channels > 1)) { + val = (bitcnt >> 1) << + TEGRA210_I2S_TIMING_CHANNEL_BIT_CNT_SHIFT; + } else + val = bitcnt << TEGRA210_I2S_TIMING_CHANNEL_BIT_CNT_SHIFT; + + if (i2sclock % (2 * srate)) + val |= TEGRA210_I2S_TIMING_NON_SYM_EN; + + regmap_write(i2s->regmap, TEGRA210_I2S_TIMING, val); + + regmap_update_bits(i2s->regmap, TEGRA210_I2S_CTRL, + TEGRA210_I2S_CTRL_FSYNC_WIDTH_MASK, + 31 << TEGRA210_I2S_CTRL_FSYNC_WIDTH_SHIFT); + + cif_conf.threshold = 0; + cif_conf.audio_channels = channels; + cif_conf.client_channels = channels; + cif_conf.audio_bits = (sample_size == 16 ? TEGRA210_AUDIOCIF_BITS_16 : + TEGRA210_AUDIOCIF_BITS_32); + + cif_conf.client_bits = (sample_size == 16 ? TEGRA210_AUDIOCIF_BITS_16 : + TEGRA210_AUDIOCIF_BITS_32); + cif_conf.expand = 0; + cif_conf.stereo_conv = 0; + cif_conf.replicate = 0; + cif_conf.truncate = 0; + cif_conf.mono_conv = 0; + + /* As a COCEC DAI, CAPTURE is transmit */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + regmap_update_bits(i2s->regmap, TEGRA210_I2S_AXBAR_RX_CTRL, + TEGRA210_I2S_AXBAR_RX_CTRL_DATA_OFFSET_MASK, + (1 << TEGRA210_I2S_AXBAR_RX_CTRL_DATA_OFFSET_SHIFT)); + + reg = TEGRA210_I2S_AXBAR_RX_CIF_CTRL; + } else { + regmap_update_bits(i2s->regmap, TEGRA210_I2S_AXBAR_TX_CTRL, + TEGRA210_I2S_AXBAR_TX_CTRL_DATA_OFFSET_MASK, + (1 << TEGRA210_I2S_AXBAR_TX_CTRL_DATA_OFFSET_SHIFT)); + + reg = TEGRA210_I2S_AXBAR_TX_CIF_CTRL; + } + + i2s->soc_data->set_audio_cif(i2s->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra210_i2s_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra210_i2s *i2s = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = i2s->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_i2s_dai_ops = { + .set_fmt = tegra210_i2s_set_fmt, + .hw_params = tegra210_i2s_hw_params, + .set_sysclk = tegra210_i2s_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver tegra210_i2s_dais[] = { + { + .name = "CIF", + .playback = { + .stream_name = "CIF Receive", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "CIF Transmit", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "DAP", + .playback = { + .stream_name = "DAP Receive", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "DAP Transmit", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra210_i2s_dai_ops, + .symmetric_rates = 1, + } +}; + +static const struct snd_kcontrol_new tegra210_i2s_controls[] = { + SOC_SINGLE("Loopback", TEGRA210_I2S_CTRL, 8, 1, 0), +}; + +static const struct snd_soc_dapm_widget tegra210_i2s_widgets[] = { + SND_SOC_DAPM_AIF_IN("CIF RX", NULL, 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_OUT("CIF TX", NULL, 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_IN("DAP RX", NULL, 0, TEGRA210_I2S_AXBAR_TX_ENABLE, + TEGRA210_I2S_AXBAR_TX_EN_SHIFT, 0), + SND_SOC_DAPM_AIF_OUT("DAP TX", NULL, 0, TEGRA210_I2S_AXBAR_RX_ENABLE, + TEGRA210_I2S_AXBAR_RX_EN_SHIFT, 0), +}; + +static const struct snd_soc_dapm_route tegra210_i2s_routes[] = { + { "CIF RX", NULL, "CIF Receive" }, + { "DAP TX", NULL, "CIF RX" }, + { "DAP Transmit", NULL, "DAP TX" }, + + { "DAP RX", NULL, "DAP Receive" }, + { "CIF TX", NULL, "DAP RX" }, + { "CIF Transmit", NULL, "CIF TX" }, +}; + +static struct snd_soc_codec_driver tegra210_i2s_codec = { + .probe = tegra210_i2s_codec_probe, + .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), +}; + +static bool tegra210_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_I2S_AXBAR_RX_ENABLE: + case TEGRA210_I2S_AXBAR_RX_INT_MASK: + case TEGRA210_I2S_AXBAR_RX_INT_SET: + case TEGRA210_I2S_AXBAR_RX_INT_CLEAR: + case TEGRA210_I2S_AXBAR_RX_CIF_CTRL: + case TEGRA210_I2S_AXBAR_RX_CTRL: + case TEGRA210_I2S_AXBAR_RX_SLOT_CTRL: + case TEGRA210_I2S_AXBAR_RX_CLK_TRIM: + case TEGRA210_I2S_AXBAR_TX_ENABLE: + case TEGRA210_I2S_AXBAR_TX_INT_MASK: + case TEGRA210_I2S_AXBAR_TX_INT_SET: + case TEGRA210_I2S_AXBAR_TX_INT_CLEAR: + case TEGRA210_I2S_AXBAR_TX_CIF_CTRL: + case TEGRA210_I2S_AXBAR_TX_CTRL: + case TEGRA210_I2S_AXBAR_TX_SLOT_CTRL: + case TEGRA210_I2S_AXBAR_TX_CLK_TRIM: + case TEGRA210_I2S_ENABLE: + case TEGRA210_I2S_SOFT_RESET: + case TEGRA210_I2S_CG: + case TEGRA210_I2S_CTRL: + case TEGRA210_I2S_TIMING: + case TEGRA210_I2S_SLOT_CTRL: + case TEGRA210_I2S_CLK_TRIM: + return true; + default: + return false; + }; +} + +static bool tegra210_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_I2S_AXBAR_RX_STATUS: + case TEGRA210_I2S_AXBAR_RX_CIF_FIFO_STATUS: + case TEGRA210_I2S_AXBAR_RX_ENABLE: + case TEGRA210_I2S_AXBAR_RX_INT_MASK: + case TEGRA210_I2S_AXBAR_RX_INT_SET: + case TEGRA210_I2S_AXBAR_RX_INT_CLEAR: + case TEGRA210_I2S_AXBAR_RX_CIF_CTRL: + case TEGRA210_I2S_AXBAR_RX_CTRL: + case TEGRA210_I2S_AXBAR_RX_SLOT_CTRL: + case TEGRA210_I2S_AXBAR_RX_CLK_TRIM: + case TEGRA210_I2S_AXBAR_RX_INT_STATUS: + case TEGRA210_I2S_AXBAR_RX_SOFT_RESET: + case TEGRA210_I2S_AXBAR_TX_STATUS: + case TEGRA210_I2S_AXBAR_TX_CIF_FIFO_STATUS: + case TEGRA210_I2S_AXBAR_TX_ENABLE: + case TEGRA210_I2S_AXBAR_TX_INT_MASK: + case TEGRA210_I2S_AXBAR_TX_INT_SET: + case TEGRA210_I2S_AXBAR_TX_INT_CLEAR: + case TEGRA210_I2S_AXBAR_TX_CIF_CTRL: + case TEGRA210_I2S_AXBAR_TX_CTRL: + case TEGRA210_I2S_AXBAR_TX_SLOT_CTRL: + case TEGRA210_I2S_AXBAR_TX_CLK_TRIM: + case TEGRA210_I2S_AXBAR_TX_INT_STATUS: + case TEGRA210_I2S_AXBAR_TX_SOFT_RESET: + case TEGRA210_I2S_ENABLE: + case TEGRA210_I2S_STATUS: + case TEGRA210_I2S_SOFT_RESET: + case TEGRA210_I2S_CG: + case TEGRA210_I2S_CTRL: + case TEGRA210_I2S_TIMING: + case TEGRA210_I2S_SLOT_CTRL: + case TEGRA210_I2S_CLK_TRIM: + 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_AXBAR_RX_INT_STATUS: + case TEGRA210_I2S_AXBAR_TX_INT_STATUS: + case TEGRA210_I2S_INT_STATUS: + case TEGRA210_I2S_AXBAR_RX_SOFT_RESET: + case TEGRA210_I2S_AXBAR_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_CLK_TRIM, + .writeable_reg = tegra210_i2s_wr_reg, + .readable_reg = tegra210_i2s_rd_reg, + .volatile_reg = tegra210_i2s_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra210_i2s_soc_data soc_data_tegra210 = { + .set_audio_cif = tegra210_xbar_set_cif, +}; + +static const struct of_device_id tegra210_i2s_of_match[] = { + { .compatible = "nvidia,tegra210-i2s", .data = &soc_data_tegra210 }, + {}, +}; + +static int tegra210_i2s_platform_probe(struct platform_device *pdev) +{ + struct tegra210_i2s *i2s; + struct resource *mem, *memregion; + void __iomem *regs; + int ret = 0; + const struct of_device_id *match; + struct tegra210_i2s_soc_data *soc_data; + + match = of_match_device(tegra210_i2s_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra210_i2s_soc_data *)match->data; + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra210_i2s), GFP_KERNEL); + if (!i2s) { + dev_err(&pdev->dev, "Can't allocate i2s\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, i2s); + + i2s->soc_data = soc_data; + + /* initialize srate with default sampling rate */ + i2s->srate = 48000; + + i2s->clk_i2s = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(i2s->clk_i2s)) { + dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); + ret = PTR_ERR(i2s->clk_i2s); + goto err; + } + + i2s->clk_i2s_sync = devm_clk_get(&pdev->dev, "ext_audio_sync"); + if (IS_ERR(i2s->clk_i2s_sync)) { + dev_err(&pdev->dev, "Can't retrieve i2s_sync clock\n"); + ret = PTR_ERR(i2s->clk_i2s_sync); + goto err_clk_put; + } + + i2s->clk_pll_a_out0 = clk_get_sys(NULL, "pll_a_out0"); + if (IS_ERR(i2s->clk_pll_a_out0)) { + dev_err(&pdev->dev, "Can't retrieve pll_a_out0 clock\n"); + ret = PTR_ERR(i2s->clk_pll_a_out0); + goto err_i2s_sync_clk_put; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_pll_a_out0_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_pll_a_out0_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_pll_a_out0_clk_put; + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra210_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(i2s->regmap); + goto err_pll_a_out0_clk_put; + } + regcache_cache_only(i2s->regmap, true); + + if (of_property_read_u32(pdev->dev.of_node, + "nvidia,ahub-i2s-id", + &pdev->dev.id) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,ahub-i2s-id\n"); + ret = -ENODEV; + goto err_pll_a_out0_clk_put; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra210_i2s_codec, + tegra210_i2s_dais, + ARRAY_SIZE(tegra210_i2s_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_i2s_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_pll_a_out0_clk_put: + clk_put(i2s->clk_pll_a_out0); +err_i2s_sync_clk_put: + devm_clk_put(&pdev->dev, i2s->clk_i2s_sync); +err_clk_put: + devm_clk_put(&pdev->dev, i2s->clk_i2s); +err: + return ret; +} + +static int tegra210_i2s_platform_remove(struct platform_device *pdev) +{ + struct tegra210_i2s *i2s = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_i2s_runtime_suspend(&pdev->dev); + + devm_clk_put(&pdev->dev, i2s->clk_i2s); + devm_clk_put(&pdev->dev, i2s->clk_i2s_sync); + clk_put(i2s->clk_pll_a_out0); + + 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) +}; + +static struct platform_driver tegra210_i2s_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_i2s_of_match, + .pm = &tegra210_i2s_pm_ops, + }, + .probe = tegra210_i2s_platform_probe, + .remove = tegra210_i2s_platform_remove, +}; +module_platform_driver(tegra210_i2s_driver) + +MODULE_AUTHOR("Songhee Baek "); +MODULE_DESCRIPTION("Tegra210 I2S ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra210_i2s_of_match); diff --git a/sound/soc/tegra-alt/tegra210_mixer_alt.c b/sound/soc/tegra-alt/tegra210_mixer_alt.c new file mode 100644 index 00000000..be8d8898 --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_mixer_alt.c @@ -0,0 +1,706 @@ +/* + * tegra210_mixer_alt.c - Tegra210 MIXER 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 "tegra210_xbar_alt.h" +#include "tegra210_mixer_alt.h" + +#define DRV_NAME "tegra210_mixer" + +#define MIXER_GAIN_CONFIG_RAM_ADDR(id) \ + (TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_ADDR_0 + \ + id*TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_ADDR_STRIDE) + +static int tegra210_mixer_runtime_suspend(struct device *dev) +{ + struct tegra210_mixer *mixer = dev_get_drvdata(dev); + + regcache_cache_only(mixer->regmap, true); + +#ifndef CONFIG_MACH_GRENADA + clk_disable_unprepare(mixer->clk_mixer); +#endif + + return 0; +} + +static int tegra210_mixer_runtime_resume(struct device *dev) +{ + struct tegra210_mixer *mixer = dev_get_drvdata(dev); +#ifndef CONFIG_MACH_GRENADA + int ret; + + ret = clk_prepare_enable(mixer->clk_mixer); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } +#endif + + regcache_cache_only(mixer->regmap, false); + + return 0; +} + +static int tegra210_mixer_write_ram(struct tegra210_mixer *mixer, + unsigned int addr, + unsigned int val) +{ + unsigned int reg, value, wait = 0xffff; + + /* check if busy */ + do { + regmap_read(mixer->regmap, + TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_CTRL, &value); + wait--; + if (!wait) + return -EINVAL; + } while (value & 0x80000000); + value = 0; + + reg = (addr << TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_CTRL_RAM_ADDR_SHIFT) & + TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_CTRL_RAM_ADDR_MASK; + reg |= TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_CTRL_ADDR_INIT_EN; + reg |= TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_CTRL_RW_WRITE; + reg |= TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_CTRL_SEQ_ACCESS_EN; + + regmap_write(mixer->regmap, + TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_CTRL, reg); + regmap_write(mixer->regmap, + TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_DATA, val); + + return 0; +} + +static int tegra210_mixer_get_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int tegra210_mixer_put_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct tegra210_mixer *mixer = snd_soc_codec_get_drvdata(codec); + unsigned int reg = mc->reg; + unsigned int ret; + + /* set the gain and trigger config */ + ret = tegra210_mixer_write_ram(mixer, reg + 0x09, + ucontrol->value.integer.value[0]); + ret |= tegra210_mixer_write_ram(mixer, reg + 0x0f, + ucontrol->value.integer.value[0]); + + return ret; +} + +static int tegra210_mixer_set_audio_cif(struct tegra210_mixer *mixer, + 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; + + mixer->soc_data->set_audio_cif(mixer->regmap, reg, &cif_conf); + return 0; +} + + +static int tegra210_mixer_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_mixer *mixer = snd_soc_dai_get_drvdata(dai); + int ret, i; + + ret = tegra210_mixer_set_audio_cif(mixer, params, + TEGRA210_MIXER_AXBAR_RX1_CIF_CTRL + + (dai->id * TEGRA210_MIXER_AXBAR_RX_STRIDE)); + + /* write the gain config poly coefficients */ + for (i = 0; i < 14; i++) { + tegra210_mixer_write_ram (mixer, + MIXER_GAIN_CONFIG_RAM_ADDR(dai->id) + i, + mixer->gain_coeff[i]); + } + + /* trigger the polynomial configuration */ + tegra210_mixer_write_ram (mixer, + MIXER_GAIN_CONFIG_RAM_ADDR(dai->id) + 0xf, + 0x01); + + return ret; +} + +static int tegra210_mixer_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra210_mixer *mixer = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = tegra210_mixer_set_audio_cif(mixer, params, + TEGRA210_MIXER_AXBAR_TX1_CIF_CTRL + + ((dai->id-10) * TEGRA210_MIXER_AXBAR_TX_STRIDE)); + + return ret; +} + +static int tegra210_mixer_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra210_mixer *mixer = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = mixer->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_mixer_out_dai_ops = { + .hw_params = tegra210_mixer_out_hw_params, +}; + +static struct snd_soc_dai_ops tegra210_mixer_in_dai_ops = { + .hw_params = tegra210_mixer_in_hw_params, +}; + +#define IN_DAI(sname, id, dai_ops) \ + { \ + .name = #sname #id, \ + .playback = { \ + .stream_name = #sname #id " Receive", \ + .channels_min = 1, \ + .channels_max = 2, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .ops = dai_ops, \ + } + +#define OUT_DAI(sname, id, dai_ops) \ + { \ + .name = #sname #id, \ + .capture = { \ + .stream_name = #sname #id " Transmit", \ + .channels_min = 1, \ + .channels_max = 2, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .ops = dai_ops, \ + } + +static struct snd_soc_dai_driver tegra210_mixer_dais[] = { + IN_DAI(RX, 1, &tegra210_mixer_in_dai_ops), + IN_DAI(RX, 2, &tegra210_mixer_in_dai_ops), + IN_DAI(RX, 3, &tegra210_mixer_in_dai_ops), + IN_DAI(RX, 4, &tegra210_mixer_in_dai_ops), + IN_DAI(RX, 5, &tegra210_mixer_in_dai_ops), + IN_DAI(RX, 6, &tegra210_mixer_in_dai_ops), + IN_DAI(RX, 7, &tegra210_mixer_in_dai_ops), + IN_DAI(RX, 8, &tegra210_mixer_in_dai_ops), + IN_DAI(RX, 9, &tegra210_mixer_in_dai_ops), + IN_DAI(RX, 10, &tegra210_mixer_in_dai_ops), + OUT_DAI(TX, 1, &tegra210_mixer_out_dai_ops), + OUT_DAI(TX, 2, &tegra210_mixer_out_dai_ops), + OUT_DAI(TX, 3, &tegra210_mixer_out_dai_ops), + OUT_DAI(TX, 4, &tegra210_mixer_out_dai_ops), + OUT_DAI(TX, 5, &tegra210_mixer_out_dai_ops), +}; + +#define ADDER_CTRL_DECL(name, reg) \ + static const struct snd_kcontrol_new name[] = { \ + SOC_DAPM_SINGLE("RX1", reg, 0, 1, 0), \ + SOC_DAPM_SINGLE("RX2", reg, 1, 1, 0), \ + SOC_DAPM_SINGLE("RX3", reg, 2, 1, 0), \ + SOC_DAPM_SINGLE("RX4", reg, 3, 1, 0), \ + SOC_DAPM_SINGLE("RX5", reg, 4, 1, 0), \ + SOC_DAPM_SINGLE("RX6", reg, 5, 1, 0), \ + SOC_DAPM_SINGLE("RX7", reg, 6, 1, 0), \ + SOC_DAPM_SINGLE("RX8", reg, 7, 1, 0), \ + SOC_DAPM_SINGLE("RX9", reg, 8, 1, 0), \ + SOC_DAPM_SINGLE("RX10", reg, 9, 1, 0), \ + } + +ADDER_CTRL_DECL(adder1, TEGRA210_MIXER_AXBAR_TX1_ADDER_CONFIG); +ADDER_CTRL_DECL(adder2, TEGRA210_MIXER_AXBAR_TX2_ADDER_CONFIG); +ADDER_CTRL_DECL(adder3, TEGRA210_MIXER_AXBAR_TX3_ADDER_CONFIG); +ADDER_CTRL_DECL(adder4, TEGRA210_MIXER_AXBAR_TX4_ADDER_CONFIG); +ADDER_CTRL_DECL(adder5, TEGRA210_MIXER_AXBAR_TX5_ADDER_CONFIG); + +static const struct snd_kcontrol_new tegra210_mixer_gain_ctls[] = { \ + SOC_SINGLE_EXT("RX1 Gain", MIXER_GAIN_CONFIG_RAM_ADDR(0), 0, 0x20000, 0, + tegra210_mixer_get_gain, tegra210_mixer_put_gain), + SOC_SINGLE_EXT("RX2 Gain", MIXER_GAIN_CONFIG_RAM_ADDR(1), 0, 0x20000, 0, + tegra210_mixer_get_gain, tegra210_mixer_put_gain), + SOC_SINGLE_EXT("RX3 Gain", MIXER_GAIN_CONFIG_RAM_ADDR(2), 0, 0x20000, 0, + tegra210_mixer_get_gain, tegra210_mixer_put_gain), + SOC_SINGLE_EXT("RX4 Gain", MIXER_GAIN_CONFIG_RAM_ADDR(3), 0, 0x20000, 0, + tegra210_mixer_get_gain, tegra210_mixer_put_gain), + SOC_SINGLE_EXT("RX5 Gain", MIXER_GAIN_CONFIG_RAM_ADDR(4), 0, 0x20000, 0, + tegra210_mixer_get_gain, tegra210_mixer_put_gain), + SOC_SINGLE_EXT("RX6 Gain", MIXER_GAIN_CONFIG_RAM_ADDR(5), 0, 0x20000, 0, + tegra210_mixer_get_gain, tegra210_mixer_put_gain), + SOC_SINGLE_EXT("RX7 Gain", MIXER_GAIN_CONFIG_RAM_ADDR(6), 0, 0x20000, 0, + tegra210_mixer_get_gain, tegra210_mixer_put_gain), + SOC_SINGLE_EXT("RX8 Gain", MIXER_GAIN_CONFIG_RAM_ADDR(7), 0, 0x20000, 0, + tegra210_mixer_get_gain, tegra210_mixer_put_gain), + SOC_SINGLE_EXT("RX9 Gain", MIXER_GAIN_CONFIG_RAM_ADDR(8), 0, 0x20000, 0, + tegra210_mixer_get_gain, tegra210_mixer_put_gain), + SOC_SINGLE_EXT("RX10 Gain", MIXER_GAIN_CONFIG_RAM_ADDR(9), 0, 0x20000, 0, + tegra210_mixer_get_gain, tegra210_mixer_put_gain), + SOC_SINGLE("Mixer Enable", TEGRA210_MIXER_ENABLE, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget tegra210_mixer_widgets[] = { + SND_SOC_DAPM_AIF_IN("RX1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX3", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX4", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX5", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX6", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX7", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX8", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX9", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX10", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX1", NULL, 0, + TEGRA210_MIXER_AXBAR_TX1_ENABLE, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX2", NULL, 0, + TEGRA210_MIXER_AXBAR_TX2_ENABLE, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX3", NULL, 0, + TEGRA210_MIXER_AXBAR_TX3_ENABLE, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX4", NULL, 0, + TEGRA210_MIXER_AXBAR_TX4_ENABLE, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX5", NULL, 0, + TEGRA210_MIXER_AXBAR_TX5_ENABLE, 0, 0), + SND_SOC_DAPM_MIXER("Adder1", SND_SOC_NOPM, 1, 0, + adder1, ARRAY_SIZE(adder1)), + SND_SOC_DAPM_MIXER("Adder2", SND_SOC_NOPM, 1, 0, + adder2, ARRAY_SIZE(adder2)), + SND_SOC_DAPM_MIXER("Adder3", SND_SOC_NOPM, 1, 0, + adder3, ARRAY_SIZE(adder3)), + SND_SOC_DAPM_MIXER("Adder4", SND_SOC_NOPM, 1, 0, + adder4, ARRAY_SIZE(adder4)), + SND_SOC_DAPM_MIXER("Adder5", SND_SOC_NOPM, 1, 0, + adder5, ARRAY_SIZE(adder5)), +}; + +#define MIXER_ROUTES(name, id) \ + {name, "RX1", "RX1",}, \ + {name, "RX2", "RX2",}, \ + {name, "RX3", "RX3",}, \ + {name, "RX4", "RX4",}, \ + {name, "RX5", "RX5",}, \ + {name, "RX6", "RX6",}, \ + {name, "RX7", "RX7",}, \ + {name, "RX8", "RX8",}, \ + {name, "RX9", "RX9",}, \ + {name, "RX10", "RX10"}, \ + {"TX"#id, NULL, name} + +static const struct snd_soc_dapm_route tegra210_mixer_routes[] = { + { "RX1", NULL, "RX1 Receive" }, + { "RX2", NULL, "RX2 Receive" }, + { "RX3", NULL, "RX3 Receive" }, + { "RX4", NULL, "RX4 Receive" }, + { "RX5", NULL, "RX5 Receive" }, + { "RX6", NULL, "RX6 Receive" }, + { "RX7", NULL, "RX7 Receive" }, + { "RX8", NULL, "RX8 Receive" }, + { "RX9", NULL, "RX9 Receive" }, + { "RX10", NULL, "RX10 Receive" }, + /* route between MIXER RXs and TXs */ + MIXER_ROUTES("Adder1", 1), + MIXER_ROUTES("Adder2", 2), + MIXER_ROUTES("Adder3", 3), + MIXER_ROUTES("Adder4", 4), + MIXER_ROUTES("Adder5", 5), + { "TX1 Transmit", NULL, "TX1" }, + { "TX2 Transmit", NULL, "TX2" }, + { "TX3 Transmit", NULL, "TX3" }, + { "TX4 Transmit", NULL, "TX4" }, + { "TX5 Transmit", NULL, "TX5" }, +}; + +static struct snd_soc_codec_driver tegra210_mixer_codec = { + .probe = tegra210_mixer_codec_probe, + .dapm_widgets = tegra210_mixer_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_mixer_widgets), + .dapm_routes = tegra210_mixer_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_mixer_routes), + .controls = tegra210_mixer_gain_ctls, + .num_controls = ARRAY_SIZE(tegra210_mixer_gain_ctls), +}; + +static bool tegra210_mixer_wr_reg(struct device *dev, + unsigned int reg) +{ + if (reg < TEGRA210_MIXER_AXBAR_RX_LIMIT) + reg %= TEGRA210_MIXER_AXBAR_RX_STRIDE; + else if (reg < TEGRA210_MIXER_AXBAR_TX_LIMIT) + reg = (reg % TEGRA210_MIXER_AXBAR_TX_STRIDE) + + TEGRA210_MIXER_AXBAR_TX1_ENABLE; + + switch (reg) { + case TEGRA210_MIXER_AXBAR_RX1_SOFT_RESET: + case TEGRA210_MIXER_AXBAR_RX1_STATUS: + case TEGRA210_MIXER_AXBAR_RX1_CIF_CTRL: + case TEGRA210_MIXER_AXBAR_RX1_CTRL: + case TEGRA210_MIXER_AXBAR_RX1_PEAK_CTRL: + + case TEGRA210_MIXER_AXBAR_TX1_ENABLE: + case TEGRA210_MIXER_AXBAR_TX1_SOFT_RESET: + case TEGRA210_MIXER_AXBAR_TX1_INT_MASK: + case TEGRA210_MIXER_AXBAR_TX1_INT_SET: + case TEGRA210_MIXER_AXBAR_TX1_INT_CLEAR: + case TEGRA210_MIXER_AXBAR_TX1_CIF_CTRL: + case TEGRA210_MIXER_AXBAR_TX1_ADDER_CONFIG: + + case TEGRA210_MIXER_ENABLE: + case TEGRA210_MIXER_SOFT_RESET: + case TEGRA210_MIXER_CG: + case TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_CTRL: + case TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_DATA: + case TEGRA210_MIXER_AHUBRAMCTL_PEAKM_RAM_CTRL: + case TEGRA210_MIXER_AHUBRAMCTL_PEAKM_RAM_DATA: + case TEGRA210_MIXER_CTRL: + return true; + default: + return false; + }; +} + +static bool tegra210_mixer_rd_reg(struct device *dev, + unsigned int reg) +{ + if (reg < TEGRA210_MIXER_AXBAR_RX_LIMIT) + reg %= TEGRA210_MIXER_AXBAR_RX_STRIDE; + else if (reg < TEGRA210_MIXER_AXBAR_TX_LIMIT) + reg = (reg % TEGRA210_MIXER_AXBAR_TX_STRIDE) + + TEGRA210_MIXER_AXBAR_TX1_ENABLE; + + switch (reg) { + case TEGRA210_MIXER_AXBAR_RX1_SOFT_RESET: + case TEGRA210_MIXER_AXBAR_RX1_STATUS: + case TEGRA210_MIXER_AXBAR_RX1_CIF_CTRL: + case TEGRA210_MIXER_AXBAR_RX1_CTRL: + case TEGRA210_MIXER_AXBAR_RX1_PEAK_CTRL: + case TEGRA210_MIXER_AXBAR_RX1_SAMPLE_COUNT: + + case TEGRA210_MIXER_AXBAR_TX1_ENABLE: + case TEGRA210_MIXER_AXBAR_TX1_SOFT_RESET: + case TEGRA210_MIXER_AXBAR_TX1_STATUS: + case TEGRA210_MIXER_AXBAR_TX1_INT_STATUS: + case TEGRA210_MIXER_AXBAR_TX1_INT_MASK: + case TEGRA210_MIXER_AXBAR_TX1_INT_SET: + case TEGRA210_MIXER_AXBAR_TX1_INT_CLEAR: + case TEGRA210_MIXER_AXBAR_TX1_CIF_CTRL: + case TEGRA210_MIXER_AXBAR_TX1_ADDER_CONFIG: + + case TEGRA210_MIXER_ENABLE: + case TEGRA210_MIXER_SOFT_RESET: + case TEGRA210_MIXER_CG: + case TEGRA210_MIXER_STATUS: + case TEGRA210_MIXER_INT_STATUS: + case TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_CTRL: + case TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_DATA: + case TEGRA210_MIXER_AHUBRAMCTL_PEAKM_RAM_CTRL: + case TEGRA210_MIXER_AHUBRAMCTL_PEAKM_RAM_DATA: + case TEGRA210_MIXER_CTRL: + return true; + default: + return false; + }; +} + +static bool tegra210_mixer_volatile_reg(struct device *dev, + unsigned int reg) +{ + if (reg < TEGRA210_MIXER_AXBAR_RX_LIMIT) + reg %= TEGRA210_MIXER_AXBAR_RX_STRIDE; + else if (reg < TEGRA210_MIXER_AXBAR_TX_LIMIT) + reg = (reg % TEGRA210_MIXER_AXBAR_TX_STRIDE) + + TEGRA210_MIXER_AXBAR_TX1_ENABLE; + + switch (reg) { + case TEGRA210_MIXER_AXBAR_RX1_SOFT_RESET: + case TEGRA210_MIXER_AXBAR_RX1_STATUS: + + case TEGRA210_MIXER_AXBAR_TX1_SOFT_RESET: + case TEGRA210_MIXER_AXBAR_TX1_STATUS: + case TEGRA210_MIXER_AXBAR_TX1_INT_STATUS: + case TEGRA210_MIXER_AXBAR_TX1_INT_SET: + + case TEGRA210_MIXER_SOFT_RESET: + case TEGRA210_MIXER_STATUS: + case TEGRA210_MIXER_INT_STATUS: + case TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_CTRL: + case TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_DATA: + case TEGRA210_MIXER_AHUBRAMCTL_PEAKM_RAM_CTRL: + case TEGRA210_MIXER_AHUBRAMCTL_PEAKM_RAM_DATA: + return true; + default: + return false; + }; +} + +static bool tegra210_mixer_precious_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA210_MIXER_AHUBRAMCTL_GAIN_CONFIG_RAM_DATA: + case TEGRA210_MIXER_AHUBRAMCTL_PEAKM_RAM_DATA: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra210_mixer_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_MIXER_CTRL, + .writeable_reg = tegra210_mixer_wr_reg, + .readable_reg = tegra210_mixer_rd_reg, + .volatile_reg = tegra210_mixer_volatile_reg, + .precious_reg = tegra210_mixer_precious_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra210_mixer_soc_data soc_data_tegra210 = { + .set_audio_cif = tegra210_xbar_set_cif +}; + +static const struct of_device_id tegra210_mixer_of_match[] = { + { .compatible = "nvidia,tegra210-amixer", .data = &soc_data_tegra210 }, + {}, +}; + +static int tegra210_mixer_platform_probe(struct platform_device *pdev) +{ + struct tegra210_mixer *mixer; + struct resource *mem, *memregion; + void __iomem *regs; + int ret; + const struct of_device_id *match; + struct tegra210_mixer_soc_data *soc_data; + + match = of_match_device(tegra210_mixer_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra210_mixer_soc_data *)match->data; + + mixer = devm_kzalloc(&pdev->dev, + sizeof(struct tegra210_mixer), GFP_KERNEL); + if (!mixer) { + dev_err(&pdev->dev, "Can't allocate tegra210_mixer\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, mixer); + + mixer->soc_data = soc_data; + mixer->gain_coeff[0] = 0x2003EEC; + mixer->gain_coeff[1] = 0x38EBC; + mixer->gain_coeff[2] = 0xFFFFFEBB; + mixer->gain_coeff[3] = 0x1414253; + mixer->gain_coeff[4] = 0xF6CEC; + mixer->gain_coeff[5] = 0xFFFFC9DE; + mixer->gain_coeff[6] = 0xC4B873; + mixer->gain_coeff[7] = 0x422F76; + mixer->gain_coeff[8] = 0xFFFA021C; + mixer->gain_coeff[9] = 0x10000; + mixer->gain_coeff[10] = 0x1A1; + mixer->gain_coeff[11] = 0x823; + mixer->gain_coeff[12] = 0x3e80; + mixer->gain_coeff[13] = 0x83126E; + +#ifndef CONFIG_MACH_GRENADA + mixer->clk_mixer = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mixer->clk_mixer)) { + dev_err(&pdev->dev, "Can't retrieve tegra210_mixer clock\n"); + ret = PTR_ERR(mixer->clk_mixer); + 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), DRV_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; + } + + mixer->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra210_mixer_regmap_config); + if (IS_ERR(mixer->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(mixer->regmap); + goto err_clk_put; + } + regcache_cache_only(mixer->regmap, true); + + if (of_property_read_u32(pdev->dev.of_node, + "nvidia,ahub-amixer-id", + &pdev->dev.id) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,ahub-amixer-id\n"); + ret = -ENODEV; + goto err_clk_put; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_mixer_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra210_mixer_codec, + tegra210_mixer_dais, + ARRAY_SIZE(tegra210_mixer_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_mixer_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, mixer->clk_mixer); +#else + dev_err(&pdev->dev, "error\n"); +#endif +err: + return ret; +} + +static int tegra210_mixer_platform_remove(struct platform_device *pdev) +{ +#ifndef CONFIG_MACH_GRENADA + struct tegra210_mixer *mixer = dev_get_drvdata(&pdev->dev); +#endif + + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_mixer_runtime_suspend(&pdev->dev); + +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, mixer->clk_mixer); +#endif + + return 0; +} + +static const struct dev_pm_ops tegra210_mixer_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_mixer_runtime_suspend, + tegra210_mixer_runtime_resume, NULL) +}; + +static struct platform_driver tegra210_mixer_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_mixer_of_match, + .pm = &tegra210_mixer_pm_ops, + }, + .probe = tegra210_mixer_platform_probe, + .remove = tegra210_mixer_platform_remove, +}; +module_platform_driver(tegra210_mixer_driver); + +MODULE_AUTHOR("Arun Shamanna Lakshmi "); +MODULE_DESCRIPTION("Tegra210 MIXER ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra210_mixer_of_match); diff --git a/sound/soc/tegra-alt/tegra210_mvc_alt.c b/sound/soc/tegra-alt/tegra210_mvc_alt.c new file mode 100644 index 00000000..3eaffcf0 --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_mvc_alt.c @@ -0,0 +1,549 @@ +/* + * tegra210_mvc_alt.c - Tegra210 MVC 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_mvc_alt.h" + +#define DRV_NAME "tegra210-mvc" + +static int tegra210_mvc_runtime_suspend(struct device *dev) +{ + struct tegra210_mvc *mvc = dev_get_drvdata(dev); + + regcache_cache_only(mvc->regmap, true); + + #ifndef CONFIG_MACH_GRENADA + clk_disable_unprepare(mvc->clk_mvc); + #endif + + return 0; +} + +static int tegra210_mvc_runtime_resume(struct device *dev) +{ + struct tegra210_mvc *mvc = dev_get_drvdata(dev); + + #ifndef CONFIG_MACH_GRENADA + int ret; + + ret = clk_prepare_enable(mvc->clk_mvc); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + #endif + + regcache_cache_only(mvc->regmap, false); + + return 0; +} + +static int tegra210_mvc_write_ram(struct tegra210_mvc *mvc, + unsigned int addr, + unsigned int val) +{ + unsigned int reg, value, wait = 0xffff; + + /* check if busy */ + do { + regmap_read(mvc->regmap, + TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_CTRL, &value); + wait--; + if (!wait) + return -EINVAL; + } while (value & 0x80000000); + value = 0; + + reg = (addr << TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_CTRL_RAM_ADDR_SHIFT) & + TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_CTRL_RAM_ADDR_MASK; + reg |= TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_CTRL_ADDR_INIT_EN; + reg |= TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_CTRL_RW_WRITE; + reg |= TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_CTRL_SEQ_ACCESS_EN; + + regmap_write(mvc->regmap, + TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_CTRL, reg); + regmap_write(mvc->regmap, + TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_DATA, val); + + return 0; +} + +static int tegra210_mvc_get_vol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int tegra210_mvc_put_vol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct tegra210_mvc *mvc = snd_soc_codec_get_drvdata(codec); + unsigned int reg = mc->reg; + unsigned int ret, value, wait = 0xffff; + + /* check if VOLUME_SWITCH is triggered*/ + do { + regmap_read(mvc->regmap, + TEGRA210_MVC_SWITCH, &value); + wait--; + if (!wait) + return -EINVAL; + } while (value & TEGRA210_MVC_VOLUME_SWITCH_MASK); + + if (reg == TEGRA210_MVC_TARGET_VOL) { + ret = regmap_write(mvc->regmap, TEGRA210_MVC_TARGET_VOL, + ucontrol->value.integer.value[0]*(1<<24)); + } else { + ret = regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL, + TEGRA210_MVC_MUTE_MASK, (ucontrol->value.integer.value[0] ? + TEGRA210_MVC_MUTE_EN : 0)); + } + ret |= regmap_update_bits(mvc->regmap, TEGRA210_MVC_SWITCH, + TEGRA210_MVC_VOLUME_SWITCH_MASK, + TEGRA210_MVC_VOLUME_SWITCH_TRIGGER); + + return ret; +} + +static int tegra210_mvc_set_audio_cif(struct tegra210_mvc *mvc, + 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; + + mvc->soc_data->set_audio_cif(mvc->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra210_mvc_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_mvc *mvc = snd_soc_dai_get_drvdata(dai); + int i, ret; + + /* set RX cif and TX cif */ + ret = tegra210_mvc_set_audio_cif(mvc, params, + TEGRA210_MVC_AXBAR_RX_CIF_CTRL); + if (ret) { + dev_err(dev, "Can't set MVC RX CIF: %d\n", ret); + return ret; + } + ret = tegra210_mvc_set_audio_cif(mvc, params, + TEGRA210_MVC_AXBAR_TX_CIF_CTRL); + if (ret) { + dev_err(dev, "Can't set MVC TX CIF: %d\n", ret); + return ret; + } + + /* disable per_channel_cntrl_en */ + regmap_update_bits(mvc->regmap, TEGRA210_MVC_CTRL, + TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK, + ~(TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK)); + + /* init the default volume=1 for MVC */ + regmap_write(mvc->regmap, TEGRA210_MVC_INIT_VOL, + TEGRA210_MVC_INIT_VOL_DEFAULT); + + /* program the poly coefficients */ + for (i = 0; i < 9; i++) + tegra210_mvc_write_ram(mvc, i, mvc->poly_coeff[i]); + + + /* program poly_n1, poly_n2, duration */ + regmap_write(mvc->regmap, TEGRA210_MVC_POLY_N1, mvc->poly_n1); + regmap_write(mvc->regmap, TEGRA210_MVC_POLY_N2, mvc->poly_n2); + regmap_write(mvc->regmap, TEGRA210_MVC_DURATION, mvc->duration); + + /* program duration_inv */ + regmap_write(mvc->regmap, TEGRA210_MVC_DURATION_INV, mvc->duration_inv); + + return ret; +} + +static int tegra210_mvc_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra210_mvc *mvc = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = mvc->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_mvc_dai_ops = { + .hw_params = tegra210_mvc_hw_params, +}; + +static const struct snd_kcontrol_new tegra210_mvc_vol_ctrl[] = { + SOC_SINGLE_EXT("Vol", TEGRA210_MVC_TARGET_VOL, 0, 100, 0, + tegra210_mvc_get_vol, tegra210_mvc_put_vol), + SOC_SINGLE_EXT("Mute", TEGRA210_MVC_CTRL, 0, 1, 0, + tegra210_mvc_get_vol, tegra210_mvc_put_vol), +}; + +static struct snd_soc_dai_driver tegra210_mvc_dais[] = { + { + .name = "MVC IN", + .playback = { + .stream_name = "MVC Receive", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "MVC OUT", + .capture = { + .stream_name = "MVC Transmit", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra210_mvc_dai_ops, + } +}; + +static const struct snd_soc_dapm_widget tegra210_mvc_widgets[] = { + SND_SOC_DAPM_AIF_IN("MVC RX", NULL, 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_OUT("MVC TX", NULL, 0, TEGRA210_MVC_ENABLE, + TEGRA210_MVC_EN_SHIFT, 0), +}; + +static const struct snd_soc_dapm_route tegra210_mvc_routes[] = { + { "MVC RX", NULL, "MVC Receive" }, + { "MVC TX", NULL, "MVC RX" }, + { "MVC Transmit", NULL, "MVC TX" }, +}; + +static struct snd_soc_codec_driver tegra210_mvc_codec = { + .probe = tegra210_mvc_codec_probe, + .dapm_widgets = tegra210_mvc_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_mvc_widgets), + .dapm_routes = tegra210_mvc_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_mvc_routes), + .controls = tegra210_mvc_vol_ctrl, + .num_controls = ARRAY_SIZE(tegra210_mvc_vol_ctrl), +}; + +static bool tegra210_mvc_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_MVC_AXBAR_RX_STATUS: + case TEGRA210_MVC_AXBAR_RX_INT_STATUS: + case TEGRA210_MVC_AXBAR_RX_INT_MASK: + case TEGRA210_MVC_AXBAR_RX_INT_SET: + case TEGRA210_MVC_AXBAR_RX_INT_CLEAR: + case TEGRA210_MVC_AXBAR_RX_CIF_CTRL: + case TEGRA210_MVC_AXBAR_RX_CYA: + case TEGRA210_MVC_AXBAR_RX_DBG: + case TEGRA210_MVC_AXBAR_TX_STATUS: + case TEGRA210_MVC_AXBAR_TX_INT_STATUS: + case TEGRA210_MVC_AXBAR_TX_INT_MASK: + case TEGRA210_MVC_AXBAR_TX_INT_SET: + case TEGRA210_MVC_AXBAR_TX_INT_CLEAR: + case TEGRA210_MVC_AXBAR_TX_CIF_CTRL: + case TEGRA210_MVC_AXBAR_TX_CYA: + case TEGRA210_MVC_AXBAR_TX_DBG: + case TEGRA210_MVC_ENABLE: + case TEGRA210_MVC_SOFT_RESET: + case TEGRA210_MVC_CG: + case TEGRA210_MVC_STATUS: + case TEGRA210_MVC_INT_STATUS: + case TEGRA210_MVC_CTRL: + case TEGRA210_MVC_SWITCH: + case TEGRA210_MVC_INIT_VOL: + case TEGRA210_MVC_TARGET_VOL: + case TEGRA210_MVC_DURATION: + case TEGRA210_MVC_DURATION_INV: + case TEGRA210_MVC_POLY_N1: + case TEGRA210_MVC_POLY_N2: + case TEGRA210_MVC_PEAK_CTRL: + case TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_CTRL: + case TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_DATA: + case TEGRA210_MVC_PEAK_VALUE: + case TEGRA210_MVC_CONFIG_ERR_TYPE: + case TEGRA210_MVC_CYA: + case TEGRA210_MVC_DBG: + return true; + default: + return false; + }; +} + +static bool tegra210_mvc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_MVC_AXBAR_RX_STATUS: + case TEGRA210_MVC_AXBAR_RX_INT_STATUS: + case TEGRA210_MVC_AXBAR_RX_INT_SET: + + case TEGRA210_MVC_AXBAR_TX_STATUS: + case TEGRA210_MVC_AXBAR_TX_INT_STATUS: + case TEGRA210_MVC_AXBAR_TX_INT_SET: + + case TEGRA210_MVC_SOFT_RESET: + case TEGRA210_MVC_STATUS: + case TEGRA210_MVC_INT_STATUS: + case TEGRA210_MVC_SWITCH: + case TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_CTRL: + case TEGRA210_MVC_AHUBRAMCTL_CONFIG_RAM_DATA: + case TEGRA210_MVC_PEAK_VALUE: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra210_mvc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_MVC_CYA, + .writeable_reg = tegra210_mvc_wr_rd_reg, + .readable_reg = tegra210_mvc_wr_rd_reg, + .volatile_reg = tegra210_mvc_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra210_mvc_soc_data soc_data_tegra210 = { + .set_audio_cif = tegra210_xbar_set_cif, +}; + +static const struct of_device_id tegra210_mvc_of_match[] = { + { .compatible = "nvidia,tegra210-mvc", .data = &soc_data_tegra210 }, + {}, +}; + +static int tegra210_mvc_platform_probe(struct platform_device *pdev) +{ + struct tegra210_mvc *mvc; + struct resource *mem, *memregion; + void __iomem *regs; + int ret = 0; + const struct of_device_id *match; + struct tegra210_mvc_soc_data *soc_data; + + match = of_match_device(tegra210_mvc_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra210_mvc_soc_data *)match->data; + + mvc = devm_kzalloc(&pdev->dev, sizeof(struct tegra210_mvc), GFP_KERNEL); + if (!mvc) { + dev_err(&pdev->dev, "Can't allocate mvc\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, mvc); + + mvc->soc_data = soc_data; + + mvc->poly_n1 = 16; + mvc->poly_n2 = 63; + mvc->duration = 150; + mvc->duration_inv = 14316558; + mvc->poly_coeff[0] = 23738319; + mvc->poly_coeff[1] = 659403; + mvc->poly_coeff[2] = -3680; + mvc->poly_coeff[3] = 15546680; + mvc->poly_coeff[4] = 2530732; + mvc->poly_coeff[5] = -120985; + mvc->poly_coeff[6] = 12048422; + mvc->poly_coeff[7] = 5527252; + mvc->poly_coeff[8] = -785042; + +#ifndef CONFIG_MACH_GRENADA + mvc->clk_mvc = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mvc->clk_mvc)) { + dev_err(&pdev->dev, "Can't retrieve mvc clock\n"); + ret = PTR_ERR(mvc->clk_mvc); + 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; + } + + mvc->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra210_mvc_regmap_config); + if (IS_ERR(mvc->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(mvc->regmap); + goto err_clk_put; + } + regcache_cache_only(mvc->regmap, true); + + if (of_property_read_u32(pdev->dev.of_node, + "nvidia,ahub-mvc-id", + &pdev->dev.id) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,ahub-mvc-id\n"); + ret = -ENODEV; + goto err_clk_put; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_mvc_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra210_mvc_codec, + tegra210_mvc_dais, + ARRAY_SIZE(tegra210_mvc_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_mvc_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, mvc->clk_mvc); +#else + dev_err(&pdev->dev, "error\n"); +#endif +err: + return ret; +} + +static int tegra210_mvc_platform_remove(struct platform_device *pdev) +{ +#ifndef CONFIG_MACH_GRENADA + struct tegra210_mvc *mvc = dev_get_drvdata(&pdev->dev); +#endif + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_mvc_runtime_suspend(&pdev->dev); + +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, mvc->clk_mvc); +#endif + + return 0; +} + +static const struct dev_pm_ops tegra210_mvc_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_mvc_runtime_suspend, + tegra210_mvc_runtime_resume, NULL) +}; + +static struct platform_driver tegra210_mvc_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_mvc_of_match, + .pm = &tegra210_mvc_pm_ops, + }, + .probe = tegra210_mvc_platform_probe, + .remove = tegra210_mvc_platform_remove, +}; +module_platform_driver(tegra210_mvc_driver) + +MODULE_AUTHOR("Arun Shamanna Lakshmi "); +MODULE_DESCRIPTION("Tegra210 MVC ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra210_mvc_of_match); diff --git a/sound/soc/tegra-alt/tegra210_sfc_alt.c b/sound/soc/tegra-alt/tegra210_sfc_alt.c new file mode 100644 index 00000000..49a3a2c4 --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_sfc_alt.c @@ -0,0 +1,584 @@ +/* + * tegra210_sfc_alt.c - Tegra210 SFC 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_sfc_alt.h" + +#define DRV_NAME "tegra210-sfc" + +static int tegra210_sfc_runtime_suspend(struct device *dev) +{ + struct tegra210_sfc *sfc = dev_get_drvdata(dev); + + regcache_cache_only(sfc->regmap, true); + +#ifndef CONFIG_MACH_GRENADA + clk_disable_unprepare(sfc->clk_sfc); +#endif + + return 0; +} + +static int tegra210_sfc_runtime_resume(struct device *dev) +{ + struct tegra210_sfc *sfc = dev_get_drvdata(dev); + +#ifndef CONFIG_MACH_GRENADA + int ret; + + ret = clk_prepare_enable(sfc->clk_sfc); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } +#endif + + regcache_cache_only(sfc->regmap, false); + + return 0; +} + +static int tegra210_sfc_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct tegra210_sfc *sfc = snd_soc_dai_get_drvdata(dai); + + if (dir == SND_SOC_CLOCK_OUT) + sfc->srate_out = freq; + else if (dir == SND_SOC_CLOCK_IN) + sfc->srate_in = freq; + + return 0; +} + +static int tegra210_sfc_set_audio_cif(struct tegra210_sfc *sfc, + 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; + + sfc->soc_data->set_audio_cif(sfc->regmap, reg, &cif_conf); + + return 0; +} + +static int tegra210_sfc_in_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_sfc *sfc = snd_soc_dai_get_drvdata(dai); + unsigned int value; + int ret; + + ret = tegra210_sfc_set_audio_cif(sfc, params, + TEGRA210_SFC_AXBAR_RX_CIF_CTRL); + if (ret) { + dev_err(dev, "Can't set SFC RX CIF: %d\n", ret); + return ret; + } + + switch (sfc->srate_in) { + case 8000: + value = TEGRA210_SFC_FS8; + break; + case 11025: + value = TEGRA210_SFC_FS11_025; + break; + case 16000: + value = TEGRA210_SFC_FS16; + break; + case 22050: + value = TEGRA210_SFC_FS22_05; + break; + case 24000: + value = TEGRA210_SFC_FS24; + break; + case 32000: + value = TEGRA210_SFC_FS32; + break; + case 44100: + value = TEGRA210_SFC_FS44_1; + break; + case 48000: + value = TEGRA210_SFC_FS48; + break; + case 64000: + value = TEGRA210_SFC_FS64; + break; + case 88200: + value = TEGRA210_SFC_FS88_2; + break; + case 96000: + value = TEGRA210_SFC_FS96; + break; + case 176400: + value = TEGRA210_SFC_FS176_4; + break; + case 192000: + value = TEGRA210_SFC_FS192; + break; + default: + value = TEGRA210_SFC_FS8; + break; + } + + regmap_write(sfc->regmap, TEGRA210_SFC_AXBAR_RX_FREQ, value); + return ret; +} + +static int tegra210_sfc_out_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_sfc *sfc = snd_soc_dai_get_drvdata(dai); + unsigned int value; + int ret; + + ret = tegra210_sfc_set_audio_cif(sfc, params, + TEGRA210_SFC_AXBAR_TX_CIF_CTRL); + if (ret) { + dev_err(dev, "Can't set SFC TX CIF: %d\n", ret); + return ret; + } + switch (sfc->srate_out) { + case 8000: + value = TEGRA210_SFC_FS8; + break; + case 11025: + value = TEGRA210_SFC_FS11_025; + break; + case 16000: + value = TEGRA210_SFC_FS16; + break; + case 22050: + value = TEGRA210_SFC_FS22_05; + break; + case 24000: + value = TEGRA210_SFC_FS24; + break; + case 32000: + value = TEGRA210_SFC_FS32; + break; + case 44100: + value = TEGRA210_SFC_FS44_1; + break; + case 48000: + value = TEGRA210_SFC_FS48; + break; + case 64000: + value = TEGRA210_SFC_FS64; + break; + case 88200: + value = TEGRA210_SFC_FS88_2; + break; + case 96000: + value = TEGRA210_SFC_FS96; + break; + case 176400: + value = TEGRA210_SFC_FS176_4; + break; + case 192000: + value = TEGRA210_SFC_FS192; + break; + default: + value = TEGRA210_SFC_FS8; + break; + } + + regmap_write(sfc->regmap, TEGRA210_SFC_AXBAR_TX_FREQ, value); + return ret; +} + +static int tegra210_sfc_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra210_sfc *sfc = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = sfc->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_sfc_in_dai_ops = { + .hw_params = tegra210_sfc_in_hw_params, + .set_sysclk = tegra210_sfc_set_dai_sysclk, +}; + +static struct snd_soc_dai_ops tegra210_sfc_out_dai_ops = { + .hw_params = tegra210_sfc_out_hw_params, +}; + +static struct snd_soc_dai_driver tegra210_sfc_dais[] = { + { + .name = "CIF", + .playback = { + .stream_name = "SFC Receive", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra210_sfc_in_dai_ops, + }, + { + .name = "DAP", + .capture = { + .stream_name = "SFC Transmit", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra210_sfc_out_dai_ops, + } +}; + +static const struct snd_soc_dapm_widget tegra210_sfc_widgets[] = { + SND_SOC_DAPM_AIF_IN("SFC RX", NULL, 0, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_AIF_OUT("SFC TX", NULL, 0, TEGRA210_SFC_ENABLE, + TEGRA210_SFC_EN_SHIFT, 0), +}; + +static const struct snd_soc_dapm_route tegra210_sfc_routes[] = { + { "SFC RX", NULL, "SFC Receive" }, + { "SFC TX", NULL, "SFC RX" }, + { "SFC Transmit", NULL, "SFC TX" }, +}; + +static struct snd_soc_codec_driver tegra210_sfc_codec = { + .probe = tegra210_sfc_codec_probe, + .dapm_widgets = tegra210_sfc_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_sfc_widgets), + .dapm_routes = tegra210_sfc_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_sfc_routes), +}; + +static bool tegra210_sfc_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_SFC_AXBAR_RX_INT_MASK: + case TEGRA210_SFC_AXBAR_RX_INT_SET: + case TEGRA210_SFC_AXBAR_RX_INT_CLEAR: + case TEGRA210_SFC_AXBAR_RX_CIF_CTRL: + case TEGRA210_SFC_AXBAR_RX_FREQ: + + case TEGRA210_SFC_AXBAR_TX_INT_MASK: + case TEGRA210_SFC_AXBAR_TX_INT_SET: + case TEGRA210_SFC_AXBAR_TX_INT_CLEAR: + case TEGRA210_SFC_AXBAR_TX_CIF_CTRL: + case TEGRA210_SFC_AXBAR_TX_FREQ: + + case TEGRA210_SFC_ENABLE: + case TEGRA210_SFC_SOFT_RESET: + case TEGRA210_SFC_CG: + case TEGRA210_SFC_COEF_RAM: + case TEGRA210_SFC_AHUBRAMCTL_SFC_CTRL: + case TEGRA210_SFC_AHUBRAMCTL_SFC_DATA: + return true; + default: + return false; + }; +} + +static bool tegra210_sfc_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_SFC_AXBAR_RX_STATUS: + case TEGRA210_SFC_AXBAR_RX_INT_STATUS: + case TEGRA210_SFC_AXBAR_RX_INT_MASK: + case TEGRA210_SFC_AXBAR_RX_INT_SET: + case TEGRA210_SFC_AXBAR_RX_INT_CLEAR: + case TEGRA210_SFC_AXBAR_RX_CIF_CTRL: + case TEGRA210_SFC_AXBAR_RX_FREQ: + + case TEGRA210_SFC_AXBAR_TX_STATUS: + case TEGRA210_SFC_AXBAR_TX_INT_STATUS: + case TEGRA210_SFC_AXBAR_TX_INT_MASK: + case TEGRA210_SFC_AXBAR_TX_INT_SET: + case TEGRA210_SFC_AXBAR_TX_INT_CLEAR: + case TEGRA210_SFC_AXBAR_TX_CIF_CTRL: + case TEGRA210_SFC_AXBAR_TX_FREQ: + + case TEGRA210_SFC_ENABLE: + case TEGRA210_SFC_SOFT_RESET: + case TEGRA210_SFC_CG: + case TEGRA210_SFC_STATUS: + case TEGRA210_SFC_INT_STATUS: + case TEGRA210_SFC_COEF_RAM: + case TEGRA210_SFC_AHUBRAMCTL_SFC_CTRL: + case TEGRA210_SFC_AHUBRAMCTL_SFC_DATA: + return true; + default: + return false; + }; +} + +static bool tegra210_sfc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_SFC_AXBAR_RX_STATUS: + case TEGRA210_SFC_AXBAR_RX_INT_STATUS: + case TEGRA210_SFC_AXBAR_RX_INT_SET: + + case TEGRA210_SFC_AXBAR_TX_STATUS: + case TEGRA210_SFC_AXBAR_TX_INT_STATUS: + case TEGRA210_SFC_AXBAR_TX_INT_SET: + + case TEGRA210_SFC_SOFT_RESET: + case TEGRA210_SFC_STATUS: + case TEGRA210_SFC_INT_STATUS: + case TEGRA210_SFC_AHUBRAMCTL_SFC_CTRL: + case TEGRA210_SFC_AHUBRAMCTL_SFC_DATA: + return true; + default: + return false; + }; +} + +static bool tegra210_sfc_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_SFC_AHUBRAMCTL_SFC_DATA: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra210_sfc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_SFC_AHUBRAMCTL_SFC_DATA, + .writeable_reg = tegra210_sfc_wr_reg, + .readable_reg = tegra210_sfc_rd_reg, + .volatile_reg = tegra210_sfc_volatile_reg, + .precious_reg = tegra210_sfc_precious_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra210_sfc_soc_data soc_data_tegra210 = { + .set_audio_cif = tegra210_xbar_set_cif, +}; + +static const struct of_device_id tegra210_sfc_of_match[] = { + { .compatible = "nvidia,tegra210-sfc", .data = &soc_data_tegra210 }, + {}, +}; + +static int tegra210_sfc_platform_probe(struct platform_device *pdev) +{ + struct tegra210_sfc *sfc; + struct resource *mem, *memregion; + void __iomem *regs; + int ret = 0; + const struct of_device_id *match; + struct tegra210_sfc_soc_data *soc_data; + + match = of_match_device(tegra210_sfc_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra210_sfc_soc_data *)match->data; + + sfc = devm_kzalloc(&pdev->dev, sizeof(struct tegra210_sfc), GFP_KERNEL); + if (!sfc) { + dev_err(&pdev->dev, "Can't allocate sfc\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, sfc); + + sfc->soc_data = soc_data; + + /* initialize default output srate */ + sfc->srate_out = 48000; + +#ifndef CONFIG_MACH_GRENADA + sfc->clk_sfc = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(sfc->clk_sfc)) { + dev_err(&pdev->dev, "Can't retrieve sfc clock\n"); + ret = PTR_ERR(sfc->clk_sfc); + 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; + } + + sfc->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra210_sfc_regmap_config); + if (IS_ERR(sfc->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(sfc->regmap); + goto err_clk_put; + } + regcache_cache_only(sfc->regmap, true); + + if (of_property_read_u32(pdev->dev.of_node, + "nvidia,ahub-sfc-id", + &pdev->dev.id) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,ahub-sfc-id\n"); + ret = -ENODEV; + goto err_clk_put; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_sfc_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra210_sfc_codec, + tegra210_sfc_dais, + ARRAY_SIZE(tegra210_sfc_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_sfc_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, sfc->clk_sfc); +#else + dev_err(&pdev->dev, "error\n"); +#endif +err: + return ret; +} + +static int tegra210_sfc_platform_remove(struct platform_device *pdev) +{ +#ifndef CONFIG_MACH_GRENADA + struct tegra210_sfc *sfc = dev_get_drvdata(&pdev->dev); +#endif + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_sfc_runtime_suspend(&pdev->dev); + +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, sfc->clk_sfc); +#endif + + return 0; +} + +static const struct dev_pm_ops tegra210_sfc_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_sfc_runtime_suspend, + tegra210_sfc_runtime_resume, NULL) +}; + +static struct platform_driver tegra210_sfc_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_sfc_of_match, + .pm = &tegra210_sfc_pm_ops, + }, + .probe = tegra210_sfc_platform_probe, + .remove = tegra210_sfc_platform_remove, +}; +module_platform_driver(tegra210_sfc_driver) + +MODULE_AUTHOR("Arun Shamanna Lakshmi "); +MODULE_DESCRIPTION("Tegra210 SFC ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra210_sfc_of_match); diff --git a/sound/soc/tegra-alt/tegra210_spdif_alt.c b/sound/soc/tegra-alt/tegra210_spdif_alt.c new file mode 100644 index 00000000..9f36efec --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_spdif_alt.c @@ -0,0 +1,501 @@ +/* + * tegra210_spdif_alt.c - Tegra210 SPDIF 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 "tegra210_xbar_alt.h" +#include "tegra210_spdif_alt.h" + +#define DRV_NAME "tegra210-spdif" + +static int tegra210_spdif_runtime_suspend(struct device *dev) +{ + struct tegra210_spdif *spdif = dev_get_drvdata(dev); + + regcache_cache_only(spdif->regmap, true); + +#ifndef CONFIG_MACH_GRENADA + clk_disable_unprepare(spdif->clk_spdif_out); + clk_disable_unprepare(spdif->clk_spdif_in); +#endif + + return 0; +} + +static int tegra210_spdif_runtime_resume(struct device *dev) +{ + struct tegra210_spdif *spdif = dev_get_drvdata(dev); + int ret = 0; + +#ifndef CONFIG_MACH_GRENADA + ret = clk_prepare_enable(spdif->clk_spdif_out); + if (ret) { + dev_err(dev, "spdif_out_clk_enable failed: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(spdif->clk_spdif_in); + if (ret) { + dev_err(dev, "spdif_in_clk_enable failed: %d\n", ret); + return ret; + } +#endif + + regcache_cache_only(spdif->regmap, false); + + return ret; +} + +static int tegra210_spdif_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + #ifndef CONFIG_MACH_GRENADA + struct device *dev = dai->dev; + struct tegra210_spdif *spdif = snd_soc_dai_get_drvdata(dai); + int spdif_out_clock_rate, spdif_in_clock_rate; + int ret; + + switch (freq) { + case 32000: + spdif_out_clock_rate = 4096000; + spdif_in_clock_rate = 48000000; + break; + case 44100: + spdif_out_clock_rate = 5644800; + spdif_in_clock_rate = 48000000; + break; + case 48000: + spdif_out_clock_rate = 6144000; + spdif_in_clock_rate = 48000000; + break; + case 88200: + spdif_out_clock_rate = 11289600; + spdif_in_clock_rate = 72000000; + break; + case 96000: + spdif_out_clock_rate = 12288000; + spdif_in_clock_rate = 72000000; + break; + case 176400: + spdif_out_clock_rate = 22579200; + spdif_in_clock_rate = 108000000; + break; + case 192000: + spdif_out_clock_rate = 24576000; + spdif_in_clock_rate = 108000000; + break; + default: + return -EINVAL; + } + + if (dir == SND_SOC_CLOCK_OUT) { + ret = clk_set_rate(spdif->clk_spdif_out, spdif_out_clock_rate); + if (ret) { + dev_err(dev, "Can't set SPDIF Out clock rate: %d\n", + ret); + return ret; + } + } else { + ret = clk_set_rate(spdif->clk_spdif_in, spdif_in_clock_rate); + if (ret) { + dev_err(dev, "Can't set SPDIF In clock rate: %d\n", + ret); + return ret; + } + } + #endif + + return 0; +} + +static int tegra210_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ +#ifndef CONFIG_MACH_GRENADA + struct device *dev = dai->dev; + struct tegra210_spdif *spdif = snd_soc_dai_get_drvdata(dai); + int channels, audio_bits, bit_mode; + struct tegra210_xbar_cif_conf cif_conf; + + channels = params_channels(params); + + if (channels < 2) { + dev_err(dev, "Doesn't support %d channels\n", channels); + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + audio_bits = TEGRA210_AUDIOCIF_BITS_16; + bit_mode = TEGRA210_SPDIF_BIT_MODE16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio_bits = TEGRA210_AUDIOCIF_BITS_32; + bit_mode = TEGRA210_SPDIF_BIT_MODERAW; + break; + default: + return -EINVAL; + } + + memset(&cif_conf, 0, sizeof(cif_conf)); + cif_conf.audio_channels = channels; + cif_conf.client_channels = channels; + cif_conf.audio_bits = audio_bits; + cif_conf.client_bits = audio_bits; + + regmap_update_bits(spdif->regmap, TEGRA210_SPDIF_CTRL, + TEGRA210_SPDIF_CTRL_BIT_MODE_MASK, + bit_mode); + + /* As a CODEC DAI, CAPTURE is transmit */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + spdif->soc_data->set_audio_cif(spdif->regmap, + TEGRA210_SPDIF_CIF_TXD_CTRL, + &cif_conf); + } else { + spdif->soc_data->set_audio_cif(spdif->regmap, + TEGRA210_SPDIF_CIF_RXD_CTRL, + &cif_conf); + } +#endif + return 0; +} + +static int tegra210_spdif_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra210_spdif *spdif = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = spdif->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_spdif_dai_ops = { + .hw_params = tegra210_spdif_hw_params, + .set_sysclk = tegra210_spdif_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver tegra210_spdif_dais[] = { + { + .name = "CIF", + .playback = { + .stream_name = "CIF Receive", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "CIF Transmit", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "DAP", + .playback = { + .stream_name = "DAP Receive", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "DAP Transmit", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra210_spdif_dai_ops, + } +}; + +static const struct snd_soc_dapm_widget tegra210_spdif_widgets[] = { + SND_SOC_DAPM_AIF_IN("CIF RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("CIF TX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DAP RX", NULL, 0, TEGRA210_SPDIF_CTRL, 29, 0), + SND_SOC_DAPM_AIF_OUT("DAP TX", NULL, 0, TEGRA210_SPDIF_CTRL, 28, 0), +}; + +static const struct snd_soc_dapm_route tegra210_spdif_routes[] = { + { "CIF RX", NULL, "CIF Receive"}, + { "DAP TX", NULL, "CIF RX"}, + { "DAP Transmit", NULL, "DAP TX"}, + + { "DAP RX", NULL, "DAP Receive"}, + { "CIF TX", NULL, "DAP RX"}, + { "CIF Transmit", NULL, "CIF TX"}, +}; + +static struct snd_soc_codec_driver tegra210_spdif_codec = { + .probe = tegra210_spdif_codec_probe, + .dapm_widgets = tegra210_spdif_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra210_spdif_widgets), + .dapm_routes = tegra210_spdif_routes, + .num_dapm_routes = ARRAY_SIZE(tegra210_spdif_routes), +}; + +static bool tegra210_spdif_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA210_SPDIF_CTRL: + case TEGRA210_SPDIF_STROBE_CTRL: + case TEGRA210_SPDIF_CIF_TXD_CTRL: + case TEGRA210_SPDIF_CIF_RXD_CTRL: + case TEGRA210_SPDIF_CIF_TXU_CTRL: + case TEGRA210_SPDIF_CIF_RXU_CTRL: + case TEGRA210_SPDIF_CH_STA_RX_A: + case TEGRA210_SPDIF_CH_STA_RX_B: + case TEGRA210_SPDIF_CH_STA_RX_C: + case TEGRA210_SPDIF_CH_STA_RX_D: + case TEGRA210_SPDIF_CH_STA_RX_E: + case TEGRA210_SPDIF_CH_STA_RX_F: + case TEGRA210_SPDIF_CH_STA_TX_A: + case TEGRA210_SPDIF_CH_STA_TX_B: + case TEGRA210_SPDIF_CH_STA_TX_C: + case TEGRA210_SPDIF_CH_STA_TX_D: + case TEGRA210_SPDIF_CH_STA_TX_E: + case TEGRA210_SPDIF_CH_STA_TX_F: + case TEGRA210_SPDIF_FLOWCTL_CTRL: + case TEGRA210_SPDIF_TX_STEP: + case TEGRA210_SPDIF_FLOW_STATUS: + case TEGRA210_SPDIF_FLOW_TOTAL: + case TEGRA210_SPDIF_FLOW_OVER: + case TEGRA210_SPDIF_FLOW_UNDER: + case TEGRA210_SPDIF_LCOEF_1_4_0: + case TEGRA210_SPDIF_LCOEF_1_4_1: + case TEGRA210_SPDIF_LCOEF_1_4_2: + case TEGRA210_SPDIF_LCOEF_1_4_3: + case TEGRA210_SPDIF_LCOEF_1_4_4: + case TEGRA210_SPDIF_LCOEF_1_4_5: + case TEGRA210_SPDIF_LCOEF_2_4_0: + case TEGRA210_SPDIF_LCOEF_2_4_1: + case TEGRA210_SPDIF_LCOEF_2_4_2: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra210_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA210_SPDIF_LCOEF_2_4_2, + .writeable_reg = tegra210_spdif_wr_rd_reg, + .readable_reg = tegra210_spdif_wr_rd_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra210_spdif_soc_data soc_data_tegra210 = { + .set_audio_cif = tegra210_xbar_set_cif, +}; + +static const struct of_device_id tegra210_spdif_of_match[] = { + { .compatible = "nvidia,tegra210-spdif", .data = &soc_data_tegra210 }, + {}, +}; + +static int tegra210_spdif_platform_probe(struct platform_device *pdev) +{ + struct tegra210_spdif *spdif; + struct resource *mem, *memregion; + void __iomem *regs; + const struct of_device_id *match; + struct tegra210_spdif_soc_data *soc_data; + int ret; + + match = of_match_device(tegra210_spdif_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra210_spdif_soc_data *)match->data; + + spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra210_spdif), + GFP_KERNEL); + if (!spdif) { + dev_err(&pdev->dev, "Can't allocate tegra210_spdif\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, spdif); + + spdif->soc_data = soc_data; + + #ifndef CONFIG_MACH_GRENADA + spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "spdif_out"); + if (IS_ERR(spdif->clk_spdif_out)) { + dev_err(&pdev->dev, "Can't retrieve spdif clock\n"); + ret = PTR_ERR(spdif->clk_spdif_out); + goto err; + } + + spdif->clk_spdif_in = devm_clk_get(&pdev->dev, "spdif_in"); + if (IS_ERR(spdif->clk_spdif_in)) { + dev_err(&pdev->dev, "Can't retrieve spdif clock\n"); + ret = PTR_ERR(spdif->clk_spdif_in); + goto err_spdif_out_clk_put; + } + #endif + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_spdif_in_clk_put; + } + + memregion = devm_request_mem_region(&pdev->dev, mem->start, + resource_size(mem), DRV_NAME); + if (!memregion) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + ret = -EBUSY; + goto err_spdif_in_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_spdif_in_clk_put; + } + + spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra210_spdif_regmap_config); + if (IS_ERR(spdif->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(spdif->regmap); + goto err_spdif_in_clk_put; + } + regcache_cache_only(spdif->regmap, true); + + if (of_property_read_u32(pdev->dev.of_node, + "nvidia,ahub-spdif-id", + &pdev->dev.id) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,ahub-spdif-id\n"); + ret = -ENODEV; + goto err_spdif_in_clk_put; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_spdif_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra210_spdif_codec, + tegra210_spdif_dais, + ARRAY_SIZE(tegra210_spdif_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_spdif_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_spdif_in_clk_put: +#ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, spdif->clk_spdif_in); +#else + dev_err(&pdev->dev, "error\n"); +#endif +#ifndef CONFIG_MACH_GRENADA +err_spdif_out_clk_put: + devm_clk_put(&pdev->dev, spdif->clk_spdif_out); +#else + dev_err(&pdev->dev, "error\n"); +#endif +err: + return ret; +} + +static int tegra210_spdif_platform_remove(struct platform_device *pdev) +{ + #ifndef CONFIG_MACH_GRENADA + struct tegra210_spdif *spdif = dev_get_drvdata(&pdev->dev); + #endif + + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_spdif_runtime_suspend(&pdev->dev); + + #ifndef CONFIG_MACH_GRENADA + devm_clk_put(&pdev->dev, spdif->clk_spdif_out); + devm_clk_put(&pdev->dev, spdif->clk_spdif_in); + #endif + + return 0; +} + +static const struct dev_pm_ops tegra210_spdif_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_spdif_runtime_suspend, + tegra210_spdif_runtime_resume, NULL) +}; + +static struct platform_driver tegra210_spdif_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_spdif_of_match, + .pm = &tegra210_spdif_pm_ops, + }, + .probe = tegra210_spdif_platform_probe, + .remove = tegra210_spdif_platform_remove, +}; +module_platform_driver(tegra210_spdif_driver); + +MODULE_AUTHOR("Arun Shamanna Lakshmi "); +MODULE_AUTHOR("Songhee Baek "); +MODULE_DESCRIPTION("Tegra210 SPDIF ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra210_spdif_of_match); diff --git a/sound/soc/tegra-alt/tegra210_xbar_alt.c b/sound/soc/tegra-alt/tegra210_xbar_alt.c new file mode 100644 index 00000000..a897f61b --- /dev/null +++ b/sound/soc/tegra-alt/tegra210_xbar_alt.c @@ -0,0 +1,854 @@ +/* + * tegra210_xbar_alt.c - Tegra210 XBAR 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 "tegra210_xbar_alt.h" + +#define DRV_NAME "tegra210-axbar" + +struct tegra210_xbar *xbar; + +static const struct regmap_config tegra210_xbar_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = TEGRA210_XBAR_PART2_RX + (TEGRA210_XBAR_RX_STRIDE * + (TEGRA210_XBAR_AUDIO_RX_COUNT - 1)), + .cache_type = REGCACHE_RBTREE, +}; + +static int tegra210_xbar_runtime_suspend(struct device *dev) +{ + + regcache_cache_only(xbar->regmap, true); + + clk_disable(xbar->clk); + + return 0; +} + +static int tegra210_xbar_runtime_resume(struct device *dev) +{ + int ret; + + ret = clk_enable(xbar->clk); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + regcache_cache_only(xbar->regmap, false); + + return 0; +} + +static int tegra210_xbar_codec_probe(struct snd_soc_codec *codec) +{ + int ret; + + codec->control_data = xbar->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; +} + +#define DAI(sname) \ + { \ + .name = #sname, \ + .playback = { \ + .stream_name = #sname " Receive", \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .capture = { \ + .stream_name = #sname " Transmit", \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + } + +static struct snd_soc_dai_driver tegra210_xbar_dais[] = { + DAI(ADMAIF1), + DAI(ADMAIF2), + DAI(ADMAIF3), + DAI(ADMAIF4), + DAI(ADMAIF5), + DAI(ADMAIF6), + DAI(ADMAIF7), + DAI(ADMAIF8), + DAI(ADMAIF9), + DAI(ADMAIF10), + DAI(I2S1), + DAI(I2S2), + DAI(I2S3), + DAI(I2S4), + DAI(I2S5), + DAI(SFC1), + DAI(SFC2), + DAI(SFC3), + DAI(SFC4), + DAI(MIXER1-1), + DAI(MIXER1-2), + DAI(MIXER1-3), + DAI(MIXER1-4), + DAI(MIXER1-5), + DAI(MIXER1-6), + DAI(MIXER1-7), + DAI(MIXER1-8), + DAI(MIXER1-9), + DAI(MIXER1-10), + DAI(SPDIF1-1), + DAI(SPDIF1-2), + DAI(AFC1), + DAI(AFC2), + DAI(AFC3), + DAI(AFC4), + DAI(AFC5), + DAI(AFC6), + DAI(OPE1), + DAI(OPE2), + DAI(SPKPROT1), + DAI(MVC1), + DAI(MVC2), + DAI(IQC1-1), + DAI(IQC1-2), + DAI(IQC2-1), + DAI(IQC2-2), + DAI(DMIC1), + DAI(DMIC2), + DAI(DMIC3), + DAI(AMX1), + DAI(AMX1-1), + DAI(AMX1-2), + DAI(AMX1-3), + DAI(AMX1-4), + DAI(AMX2), + DAI(AMX2-1), + DAI(AMX2-2), + DAI(AMX2-3), + DAI(AMX2-4), + DAI(ADX1-1), + DAI(ADX1-2), + DAI(ADX1-3), + DAI(ADX1-4), + DAI(ADX1), + DAI(ADX2-1), + DAI(ADX2-2), + DAI(ADX2-3), + DAI(ADX2-4), + DAI(ADX2), +}; + +static const char * const tegra210_xbar_mux_texts[] = { + "None", + "ADMAIF1", + "ADMAIF2", + "ADMAIF3", + "ADMAIF4", + "ADMAIF5", + "ADMAIF6", + "ADMAIF7", + "ADMAIF8", + "ADMAIF9", + "ADMAIF10", + "I2S1", + "I2S2", + "I2S3", + "I2S4", + "I2S5", + "SFC1", + "SFC2", + "SFC3", + "SFC4", + /* index 0..19 above are inputs of PART0 Mux */ + "MIXER1-1", + "MIXER1-2", + "MIXER1-3", + "MIXER1-4", + "MIXER1-5", + "AMX1", + "AMX2", + "SPDIF1-1", + "SPDIF1-2", + "AFC1", + "AFC2", + "AFC3", + "AFC4", + "AFC5", + "AFC6", + /* index 20..34 above are inputs of PART1 Mux */ + "OPE1", + "OPE2", + "SPKPROT1", + "MVC1", + "MVC2", + "IQC1-1", + "IQC1-2", + "IQC2-1", + "IQC2-2", + "DMIC1", + "DMIC2", + "DMIC3", + "ADX1-1", + "ADX1-2", + "ADX1-3", + "ADX1-4", + "ADX2-1", + "ADX2-2", + "ADX2-3", + "ADX2-4", + /* index 35..53 above are inputs of PART2 Mux */ +}; + +#define MUX_VALUE(npart, nbit) (1 + nbit + npart * 32) +static const int tegra210_xbar_mux_values[] = { + /* Mux0 input, Mux1 input, Mux2 input */ + 0, + MUX_VALUE(0, 0), + MUX_VALUE(0, 1), + MUX_VALUE(0, 2), + MUX_VALUE(0, 3), + MUX_VALUE(0, 4), + MUX_VALUE(0, 5), + MUX_VALUE(0, 6), + MUX_VALUE(0, 7), + MUX_VALUE(0, 8), + MUX_VALUE(0, 9), + MUX_VALUE(0, 16), + MUX_VALUE(0, 17), + MUX_VALUE(0, 18), + MUX_VALUE(0, 19), + MUX_VALUE(0, 20), + MUX_VALUE(0, 24), + MUX_VALUE(0, 25), + MUX_VALUE(0, 26), + MUX_VALUE(0, 27), + /* index 0..19 above are inputs of PART0 Mux */ + MUX_VALUE(1, 0), + MUX_VALUE(1, 1), + MUX_VALUE(1, 2), + MUX_VALUE(1, 3), + MUX_VALUE(1, 4), + MUX_VALUE(1, 8), + MUX_VALUE(1, 9), + MUX_VALUE(1, 20), + MUX_VALUE(1, 21), + MUX_VALUE(1, 24), + MUX_VALUE(1, 25), + MUX_VALUE(1, 26), + MUX_VALUE(1, 27), + MUX_VALUE(1, 28), + MUX_VALUE(1, 29), + /* index 20..34 above are inputs of PART1 Mux */ + MUX_VALUE(2, 0), + MUX_VALUE(2, 1), + MUX_VALUE(2, 4), + MUX_VALUE(2, 8), + MUX_VALUE(2, 9), + MUX_VALUE(2, 12), + MUX_VALUE(2, 13), + MUX_VALUE(2, 14), + MUX_VALUE(2, 15), + MUX_VALUE(2, 18), + MUX_VALUE(2, 19), + MUX_VALUE(2, 20), + MUX_VALUE(2, 24), + MUX_VALUE(2, 25), + MUX_VALUE(2, 26), + MUX_VALUE(2, 27), + MUX_VALUE(2, 28), + MUX_VALUE(2, 29), + MUX_VALUE(2, 30), + MUX_VALUE(2, 31), + /* index 35..53 above are inputs of PART2 Mux */ +}; + +#define MUX0_REG(id) (TEGRA210_XBAR_PART0_RX + \ + (TEGRA210_XBAR_RX_STRIDE * (id))) + +#define MUX1_REG(id) (TEGRA210_XBAR_PART1_RX + \ + (TEGRA210_XBAR_RX_STRIDE * (id))) + +#define MUX2_REG(id) (TEGRA210_XBAR_PART2_RX + \ + (TEGRA210_XBAR_RX_STRIDE * (id))) + +#define MUX_ENUM_CTRL_DECL(ename, id) \ + int ename##_regs[3] = { MUX0_REG(id), MUX1_REG(id), MUX2_REG(id) }; \ + unsigned int ename##_masks[3] = { 0xf1f03ff, 0x3f30031f, 0xff1cf313 }; \ + SOC_VALUE_ENUM_ONEHOT_DECL(ename##_enum, ename##_regs, ename##_masks, 3, \ + tegra210_xbar_mux_texts, tegra210_xbar_mux_values); \ + static const struct snd_kcontrol_new ename##_control = \ + SOC_DAPM_ENUM_ONEHOT("Route", ename##_enum) + +MUX_ENUM_CTRL_DECL(admaif1_tx, 0x00); +MUX_ENUM_CTRL_DECL(admaif2_tx, 0x01); +MUX_ENUM_CTRL_DECL(admaif3_tx, 0x02); +MUX_ENUM_CTRL_DECL(admaif4_tx, 0x03); +MUX_ENUM_CTRL_DECL(admaif5_tx, 0x04); +MUX_ENUM_CTRL_DECL(admaif6_tx, 0x05); +MUX_ENUM_CTRL_DECL(admaif7_tx, 0x06); +MUX_ENUM_CTRL_DECL(admaif8_tx, 0x07); +MUX_ENUM_CTRL_DECL(admaif9_tx, 0x08); +MUX_ENUM_CTRL_DECL(admaif10_tx, 0x09); +MUX_ENUM_CTRL_DECL(i2s1_tx, 0x10); +MUX_ENUM_CTRL_DECL(i2s2_tx, 0x11); +MUX_ENUM_CTRL_DECL(i2s3_tx, 0x12); +MUX_ENUM_CTRL_DECL(i2s4_tx, 0x13); +MUX_ENUM_CTRL_DECL(i2s5_tx, 0x14); +MUX_ENUM_CTRL_DECL(sfc1_tx, 0x18); +MUX_ENUM_CTRL_DECL(sfc2_tx, 0x19); +MUX_ENUM_CTRL_DECL(sfc3_tx, 0x1a); +MUX_ENUM_CTRL_DECL(sfc4_tx, 0x1b); +MUX_ENUM_CTRL_DECL(mixer11_tx, 0x20); +MUX_ENUM_CTRL_DECL(mixer12_tx, 0x21); +MUX_ENUM_CTRL_DECL(mixer13_tx, 0x22); +MUX_ENUM_CTRL_DECL(mixer14_tx, 0x23); +MUX_ENUM_CTRL_DECL(mixer15_tx, 0x24); +MUX_ENUM_CTRL_DECL(mixer16_tx, 0x25); +MUX_ENUM_CTRL_DECL(mixer17_tx, 0x26); +MUX_ENUM_CTRL_DECL(mixer18_tx, 0x27); +MUX_ENUM_CTRL_DECL(mixer19_tx, 0x28); +MUX_ENUM_CTRL_DECL(mixer110_tx, 0x29); +MUX_ENUM_CTRL_DECL(spdif11_tx, 0x30); +MUX_ENUM_CTRL_DECL(spdif12_tx, 0x31); +MUX_ENUM_CTRL_DECL(afc1_tx, 0x34); +MUX_ENUM_CTRL_DECL(afc2_tx, 0x35); +MUX_ENUM_CTRL_DECL(afc3_tx, 0x36); +MUX_ENUM_CTRL_DECL(afc4_tx, 0x37); +MUX_ENUM_CTRL_DECL(afc5_tx, 0x38); +MUX_ENUM_CTRL_DECL(afc6_tx, 0x39); +MUX_ENUM_CTRL_DECL(ope1_tx, 0x40); +MUX_ENUM_CTRL_DECL(ope2_tx, 0x41); +MUX_ENUM_CTRL_DECL(spkprot_tx, 0x44); +MUX_ENUM_CTRL_DECL(mvc1_tx, 0x48); +MUX_ENUM_CTRL_DECL(mvc2_tx, 0x49); +MUX_ENUM_CTRL_DECL(amx11_tx, 0x50); +MUX_ENUM_CTRL_DECL(amx12_tx, 0x51); +MUX_ENUM_CTRL_DECL(amx13_tx, 0x52); +MUX_ENUM_CTRL_DECL(amx14_tx, 0x53); +MUX_ENUM_CTRL_DECL(amx21_tx, 0x54); +MUX_ENUM_CTRL_DECL(amx22_tx, 0x55); +MUX_ENUM_CTRL_DECL(amx23_tx, 0x56); +MUX_ENUM_CTRL_DECL(amx24_tx, 0x57); +MUX_ENUM_CTRL_DECL(adx1_tx, 0x58); +MUX_ENUM_CTRL_DECL(adx2_tx, 0x59); + +#define WIDGETS(sname, ename) \ + SND_SOC_DAPM_AIF_IN(sname " RX", NULL, 0, SND_SOC_NOPM, 0, 0), \ + SND_SOC_DAPM_AIF_OUT(sname " TX", NULL, 0, SND_SOC_NOPM, 0, 0), \ + SND_SOC_DAPM_VALUE_MUX(sname " Mux", SND_SOC_NOPM, 0, 0, &ename##_control) + +#define TX_WIDGETS(sname) \ + SND_SOC_DAPM_AIF_IN(sname " RX", NULL, 0, SND_SOC_NOPM, 0, 0), \ + SND_SOC_DAPM_AIF_OUT(sname " TX", NULL, 0, SND_SOC_NOPM, 0, 0) + +/* + * The number of entries in, and order of, this array is closely tied to the + * calculation of tegra210_xbar_codec.num_dapm_widgets near the end of + * tegra210_xbar_probe() + */ +static const struct snd_soc_dapm_widget tegra210_xbar_widgets[] = { + WIDGETS("ADMAIF1", admaif1_tx), + WIDGETS("ADMAIF2", admaif2_tx), + WIDGETS("ADMAIF3", admaif3_tx), + WIDGETS("ADMAIF4", admaif4_tx), + WIDGETS("ADMAIF5", admaif5_tx), + WIDGETS("ADMAIF6", admaif6_tx), + WIDGETS("ADMAIF7", admaif7_tx), + WIDGETS("ADMAIF8", admaif8_tx), + WIDGETS("ADMAIF9", admaif9_tx), + WIDGETS("ADMAIF10", admaif10_tx), + WIDGETS("I2S1", i2s1_tx), + WIDGETS("I2S2", i2s2_tx), + WIDGETS("I2S3", i2s3_tx), + WIDGETS("I2S4", i2s4_tx), + WIDGETS("I2S5", i2s5_tx), + WIDGETS("SFC1", sfc1_tx), + WIDGETS("SFC2", sfc2_tx), + WIDGETS("SFC3", sfc3_tx), + WIDGETS("SFC4", sfc4_tx), + WIDGETS("MIXER1-1", mixer11_tx), + WIDGETS("MIXER1-2", mixer12_tx), + WIDGETS("MIXER1-3", mixer13_tx), + WIDGETS("MIXER1-4", mixer14_tx), + WIDGETS("MIXER1-5", mixer15_tx), + WIDGETS("MIXER1-6", mixer16_tx), + WIDGETS("MIXER1-7", mixer17_tx), + WIDGETS("MIXER1-8", mixer18_tx), + WIDGETS("MIXER1-9", mixer19_tx), + WIDGETS("MIXER1-10", mixer110_tx), + WIDGETS("SPDIF1-1", spdif11_tx), + WIDGETS("SPDIF1-2", spdif12_tx), + WIDGETS("AFC1", afc1_tx), + WIDGETS("AFC2", afc2_tx), + WIDGETS("AFC3", afc3_tx), + WIDGETS("AFC4", afc4_tx), + WIDGETS("AFC5", afc5_tx), + WIDGETS("AFC6", afc6_tx), + WIDGETS("OPE1", ope1_tx), + WIDGETS("OPE2", ope2_tx), + WIDGETS("SPKPROT1", spkprot_tx), + WIDGETS("MVC1", mvc1_tx), + WIDGETS("MVC2", mvc2_tx), + WIDGETS("AMX1-1", amx11_tx), + WIDGETS("AMX1-2", amx12_tx), + WIDGETS("AMX1-3", amx13_tx), + WIDGETS("AMX1-4", amx14_tx), + WIDGETS("AMX2-1", amx21_tx), + WIDGETS("AMX2-2", amx22_tx), + WIDGETS("AMX2-3", amx23_tx), + WIDGETS("AMX2-4", amx24_tx), + WIDGETS("ADX1", adx1_tx), + WIDGETS("ADX2", adx2_tx), + TX_WIDGETS("IQC1-1"), + TX_WIDGETS("IQC1-2"), + TX_WIDGETS("IQC2-1"), + TX_WIDGETS("IQC2-2"), + TX_WIDGETS("DMIC1"), + TX_WIDGETS("DMIC2"), + TX_WIDGETS("DMIC3"), + TX_WIDGETS("AMX1"), + TX_WIDGETS("ADX1-1"), + TX_WIDGETS("ADX1-2"), + TX_WIDGETS("ADX1-3"), + TX_WIDGETS("ADX1-4"), + TX_WIDGETS("AMX2"), + TX_WIDGETS("ADX2-1"), + TX_WIDGETS("ADX2-2"), + TX_WIDGETS("ADX2-3"), + TX_WIDGETS("ADX2-4"), +}; + +#define TEGRA210_ROUTES(name) \ + { name " RX", NULL, name " Receive"}, \ + { name " Transmit", NULL, name " TX"}, \ + { name " TX", NULL, name " Mux" }, \ + { name " Mux", "ADMAIF1", "ADMAIF1 RX" }, \ + { name " Mux", "ADMAIF2", "ADMAIF2 RX" }, \ + { name " Mux", "ADMAIF3", "ADMAIF3 RX" }, \ + { name " Mux", "ADMAIF4", "ADMAIF4 RX" }, \ + { name " Mux", "ADMAIF5", "ADMAIF5 RX" }, \ + { name " Mux", "ADMAIF6", "ADMAIF6 RX" }, \ + { name " Mux", "ADMAIF7", "ADMAIF7 RX" }, \ + { name " Mux", "ADMAIF8", "ADMAIF8 RX" }, \ + { name " Mux", "ADMAIF9", "ADMAIF9 RX" }, \ + { name " Mux", "ADMAIF10", "ADMAIF10 RX" }, \ + { name " Mux", "I2S1", "I2S1 RX" }, \ + { name " Mux", "I2S2", "I2S2 RX" }, \ + { name " Mux", "I2S3", "I2S3 RX" }, \ + { name " Mux", "I2S4", "I2S4 RX" }, \ + { name " Mux", "I2S5", "I2S5 RX" }, \ + { name " Mux", "SFC1", "SFC1 RX" }, \ + { name " Mux", "SFC2", "SFC2 RX" }, \ + { name " Mux", "SFC3", "SFC3 RX" }, \ + { name " Mux", "SFC4", "SFC4 RX" }, \ + { name " Mux", "MIXER1-1", "MIXER1-1 RX" }, \ + { name " Mux", "MIXER1-2", "MIXER1-2 RX" }, \ + { name " Mux", "MIXER1-3", "MIXER1-3 RX" }, \ + { name " Mux", "MIXER1-4", "MIXER1-4 RX" }, \ + { name " Mux", "MIXER1-5", "MIXER1-5 RX" }, \ + { name " Mux", "SPDIF1-1", "SPDIF1-1 RX" }, \ + { name " Mux", "SPDIF1-2", "SPDIF1-2 RX" }, \ + { name " Mux", "AFC1", "AFC1 RX" }, \ + { name " Mux", "AFC2", "AFC2 RX" }, \ + { name " Mux", "AFC3", "AFC3 RX" }, \ + { name " Mux", "AFC4", "AFC4 RX" }, \ + { name " Mux", "AFC5", "AFC5 RX" }, \ + { name " Mux", "AFC6", "AFC6 RX" }, \ + { name " Mux", "OPE1", "OPE1 RX" }, \ + { name " Mux", "OPE2", "OPE2 RX" }, \ + { name " Mux", "MVC1", "MVC1 RX" }, \ + { name " Mux", "MVC2", "MVC2 RX" }, \ + { name " Mux", "IQC1-1", "IQC1-1 RX" }, \ + { name " Mux", "IQC1-2", "IQC1-2 RX" }, \ + { name " Mux", "IQC2-1", "IQC2-1 RX" }, \ + { name " Mux", "IQC2-2", "IQC2-2 RX" }, \ + { name " Mux", "AMX1", "AMX1 RX" }, \ + { name " Mux", "ADX1-1", "ADX1-1 RX" }, \ + { name " Mux", "ADX1-2", "ADX1-2 RX" }, \ + { name " Mux", "ADX1-3", "ADX1-3 RX" }, \ + { name " Mux", "ADX1-4", "ADX1-4 RX" }, \ + { name " Mux", "AMX2", "AMX1 RX" }, \ + { name " Mux", "ADX2-1", "ADX1-1 RX" }, \ + { name " Mux", "ADX2-2", "ADX1-2 RX" }, \ + { name " Mux", "ADX2-3", "ADX1-3 RX" }, \ + { name " Mux", "ADX2-4", "ADX1-4 RX" }, + + +#define IN_OUT_ROUTES(name) \ + { name " RX", NULL, name " Receive" }, \ + { name " Transmit", NULL, name " TX" }, + +/* + * The number of entries in, and order of, this array is closely tied to the + * calculation of tegra210_xbar_codec.num_dapm_routes near the end of + * tegra210_xbar_probe() + */ +static const struct snd_soc_dapm_route tegra210_xbar_routes[] = { + TEGRA210_ROUTES("ADMAIF1") + TEGRA210_ROUTES("ADMAIF2") + TEGRA210_ROUTES("ADMAIF3") + TEGRA210_ROUTES("ADMAIF4") + TEGRA210_ROUTES("ADMAIF5") + TEGRA210_ROUTES("ADMAIF6") + TEGRA210_ROUTES("ADMAIF7") + TEGRA210_ROUTES("ADMAIF8") + TEGRA210_ROUTES("ADMAIF9") + TEGRA210_ROUTES("ADMAIF10") + TEGRA210_ROUTES("I2S1") + TEGRA210_ROUTES("I2S2") + TEGRA210_ROUTES("I2S3") + TEGRA210_ROUTES("I2S4") + TEGRA210_ROUTES("I2S5") + TEGRA210_ROUTES("SFC1") + TEGRA210_ROUTES("SFC2") + TEGRA210_ROUTES("SFC3") + TEGRA210_ROUTES("SFC4") + TEGRA210_ROUTES("MIXER1-1") + TEGRA210_ROUTES("MIXER1-2") + TEGRA210_ROUTES("MIXER1-3") + TEGRA210_ROUTES("MIXER1-4") + TEGRA210_ROUTES("MIXER1-5") + TEGRA210_ROUTES("MIXER1-6") + TEGRA210_ROUTES("MIXER1-7") + TEGRA210_ROUTES("MIXER1-8") + TEGRA210_ROUTES("MIXER1-9") + TEGRA210_ROUTES("MIXER1-10") + TEGRA210_ROUTES("SPDIF1-1") + TEGRA210_ROUTES("SPDIF1-2") + TEGRA210_ROUTES("AFC1") + TEGRA210_ROUTES("AFC2") + TEGRA210_ROUTES("AFC3") + TEGRA210_ROUTES("AFC4") + TEGRA210_ROUTES("AFC5") + TEGRA210_ROUTES("AFC6") + TEGRA210_ROUTES("OPE1") + TEGRA210_ROUTES("OPE2") + TEGRA210_ROUTES("SPKPROT1") + TEGRA210_ROUTES("MVC1") + TEGRA210_ROUTES("MVC2") + TEGRA210_ROUTES("AMX1-1") + TEGRA210_ROUTES("AMX1-2") + TEGRA210_ROUTES("AMX1-3") + TEGRA210_ROUTES("AMX1-4") + TEGRA210_ROUTES("AMX2-1") + TEGRA210_ROUTES("AMX2-2") + TEGRA210_ROUTES("AMX2-3") + TEGRA210_ROUTES("AMX2-4") + TEGRA210_ROUTES("ADX1") + TEGRA210_ROUTES("ADX2") + IN_OUT_ROUTES("IQC1-1") + IN_OUT_ROUTES("IQC1-2") + IN_OUT_ROUTES("IQC2-1") + IN_OUT_ROUTES("IQC2-1") + IN_OUT_ROUTES("DMIC1") + IN_OUT_ROUTES("DMIC2") + IN_OUT_ROUTES("DMIC3") + IN_OUT_ROUTES("AMX1") + IN_OUT_ROUTES("AMX2") + IN_OUT_ROUTES("ADX1-1") + IN_OUT_ROUTES("ADX1-2") + IN_OUT_ROUTES("ADX1-3") + IN_OUT_ROUTES("ADX1-4") + IN_OUT_ROUTES("ADX2-1") + IN_OUT_ROUTES("ADX2-2") + IN_OUT_ROUTES("ADX2-3") + IN_OUT_ROUTES("ADX2-4") +}; + +static struct snd_soc_codec_driver tegra210_xbar_codec = { + .probe = tegra210_xbar_codec_probe, + .dapm_widgets = tegra210_xbar_widgets, + .dapm_routes = tegra210_xbar_routes, + .num_dapm_widgets = ARRAY_SIZE(tegra210_xbar_widgets), + .num_dapm_routes = ARRAY_SIZE(tegra210_xbar_routes), +}; + +static const struct tegra210_xbar_soc_data soc_data_tegra210 = { + .regmap_config = &tegra210_xbar_regmap_config, +}; + +static const struct of_device_id tegra210_xbar_of_match[] = { + { .compatible = "nvidia,tegra210-axbar", .data = &soc_data_tegra210 }, + {}, +}; + +#define CLK_LIST_MASK_TEGRA30 BIT(0) +#define CLK_LIST_MASK_TEGRA114 BIT(1) +#define CLK_LIST_MASK_TEGRA124 BIT(2) + +#define CLK_LIST_MASK_TEGRA30_OR_LATER \ + (CLK_LIST_MASK_TEGRA30 | CLK_LIST_MASK_TEGRA114 |\ + CLK_LIST_MASK_TEGRA124) +#define CLK_LIST_MASK_TEGRA114_OR_LATER \ + (CLK_LIST_MASK_TEGRA114 | CLK_LIST_MASK_TEGRA124) + +static const struct { + const char *clk_name; +} configlink_clocks[] = { + { "ape" }, +}; + +/* FIXME: base address for T210 */ +struct of_dev_auxdata tegra210_xbar_auxdata[] = { + OF_DEV_AUXDATA("nvidia,tegra210-admaif", 0x702d0000, "tegra210-admaif", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-i2s", 0x702d1000, "tegra210-i2s.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-i2s", 0x702d1100, "tegra210-i2s.1", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-i2s", 0x702d1200, "tegra210-i2s.2", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-i2s", 0x702d1300, "tegra210-i2s.3", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-i2s", 0x702d1400, "tegra210-i2s.4", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-amx", 0x702d3000, "tegra210-amx.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-amx", 0x702d3100, "tegra210-amx.1", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-adx", 0x702d3800, "tegra210-adx.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-adx", 0x702d3900, "tegra210-adx.1", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-afc", 0x702d7000, "tegra210-afc.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-afc", 0x702d7100, "tegra210-afc.1", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-afc", 0x702d7200, "tegra210-afc.2", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-afc", 0x702d7300, "tegra210-afc.3", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-afc", 0x702d7400, "tegra210-afc.4", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-afc", 0x702d7500, "tegra210-afc.5", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-sfc", 0x702d2000, "tegra210-sfc.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-sfc", 0x702d2200, "tegra210-sfc.1", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-sfc", 0x702d2400, "tegra210-sfc.2", NULL), + 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-amixer", 0x702dbb00, "tegra210-mixer", NULL), + OF_DEV_AUXDATA("nvidia,tegra210-spdif", 0x702d6000, "tegra210-spdif", NULL), + {} +}; + +static int tegra210_xbar_probe(struct platform_device *pdev) +{ + struct clk *clk; + void __iomem *regs; + int ret, i; + const struct of_device_id *match; + struct tegra210_xbar_soc_data *soc_data; + struct clk *parent_clk; + + match = of_match_device(tegra210_xbar_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra210_xbar_soc_data *)match->data; + + /* + * The TEGRA APE XBAR client a register bus: the "configlink". + * For this to operate correctly, all devices on this bus must + * be out of reset. + * Ensure that here. + */ + for (i = 0; i < ARRAY_SIZE(configlink_clocks); i++) { + clk = devm_clk_get(&pdev->dev, configlink_clocks[i].clk_name); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Can't get clock %s\n", + configlink_clocks[i].clk_name); + ret = PTR_ERR(clk); + goto err; + } + tegra_periph_reset_deassert(clk); + devm_clk_put(&pdev->dev, clk); + } + + xbar = devm_kzalloc(&pdev->dev, sizeof(*xbar), GFP_KERNEL); + if (!xbar) { + dev_err(&pdev->dev, "Can't allocate xbar\n"); + ret = -ENOMEM; + goto err; + } + + xbar->soc_data = soc_data; + + xbar->clk = devm_clk_get(&pdev->dev, "d_audio"); + if (IS_ERR(xbar->clk)) { + dev_err(&pdev->dev, "Can't retrieve clock\n"); + ret = PTR_ERR(xbar->clk); + goto err; + } + + xbar->clk_parent = clk_get_sys(NULL, "pll_a_out0"); + if (IS_ERR(xbar->clk)) { + dev_err(&pdev->dev, "Can't retrieve pll_a_out0 clock\n"); + ret = PTR_ERR(xbar->clk_parent); + goto err_clk_put; + } + + parent_clk = clk_get_parent(xbar->clk); + if (IS_ERR(parent_clk)) { + dev_err(&pdev->dev, "Can't get parent clock fo xbar\n"); + ret = PTR_ERR(parent_clk); + goto err_clk_put; + } + + ret = clk_set_rate(xbar->clk_parent, 24560000); + if (ret) { + dev_err(&pdev->dev, "Failed to set clock rate of pll_a_out0\n"); + goto err_clk_put; + } + + ret = clk_set_parent(xbar->clk, xbar->clk_parent); + if (ret) { + dev_err(&pdev->dev, "Failed to set parent clock with pll_a_out0\n"); + goto err_clk_put; + } + + regs = devm_request_and_ioremap(&pdev->dev, pdev->resource); + if (!regs) { + dev_err(&pdev->dev, "request/iomap region failed\n"); + ret = -ENODEV; + goto err_clk_set_parent; + } + + xbar->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + soc_data->regmap_config); + if (IS_ERR(xbar->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(xbar->regmap); + goto err_clk_put_parent; + } + regcache_cache_only(xbar->regmap, true); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra210_xbar_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra210_xbar_codec, + tegra210_xbar_dais, ARRAY_SIZE(tegra210_xbar_dais)); + if (ret != 0) { + dev_err(&pdev->dev, "Could not register CODEC: %d\n", ret); + goto err_suspend; + } + + of_platform_populate(pdev->dev.of_node, NULL, tegra210_xbar_auxdata, + &pdev->dev); + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_xbar_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put_parent: + clk_put(xbar->clk_parent); +err_clk_set_parent: + clk_set_parent(xbar->clk, parent_clk); +err_clk_put: + devm_clk_put(&pdev->dev, xbar->clk); +err: + return ret; +} + +static int tegra210_xbar_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra210_xbar_runtime_suspend(&pdev->dev); + + devm_clk_put(&pdev->dev, xbar->clk); + clk_put(xbar->clk_parent); + + return 0; +} + +static const struct dev_pm_ops tegra210_xbar_pm_ops = { + SET_RUNTIME_PM_OPS(tegra210_xbar_runtime_suspend, + tegra210_xbar_runtime_resume, NULL) +}; + +static struct platform_driver tegra210_xbar_driver = { + .probe = tegra210_xbar_probe, + .remove = tegra210_xbar_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra210_xbar_of_match, + .pm = &tegra210_xbar_pm_ops, + }, +}; +module_platform_driver(tegra210_xbar_driver); + +void tegra210_xbar_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra210_xbar_cif_conf *conf) +{ + unsigned int value; + + value = (conf->threshold << + TEGRA210_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | + ((conf->audio_channels - 1) << + TEGRA210_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | + ((conf->client_channels - 1) << + TEGRA210_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | + (conf->audio_bits << + TEGRA210_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) | + (conf->client_bits << + TEGRA210_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) | + (conf->expand << + TEGRA210_AUDIOCIF_CTRL_EXPAND_SHIFT) | + (conf->stereo_conv << + TEGRA210_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) | + (conf->replicate << + TEGRA210_AUDIOCIF_CTRL_REPLICATE_SHIFT) | + (conf->truncate << + TEGRA210_AUDIOCIF_CTRL_TRUNCATE_SHIFT) | + (conf->mono_conv << + TEGRA210_AUDIOCIF_CTRL_MONO_CONV_SHIFT); + + regmap_update_bits(regmap, reg, 0x3fffffff, value); +} +EXPORT_SYMBOL_GPL(tegra210_xbar_set_cif); + +int tegra210_xbar_read_reg (unsigned int reg, unsigned int *val) +{ + int ret; + + ret = regmap_read (xbar->regmap, reg, val); + return ret; +} +EXPORT_SYMBOL_GPL(tegra210_xbar_read_reg); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra210 XBAR driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME);