ASoC: tegra-alt: T210 AHUB drivers

This driver is for T210 AHUB components.

Bug 1442940

Change-Id: If6b33bc89cfc273f8024b7e4f6d644c170c2fe4f
Signed-off-by: Songhee Baek <sbaek@nvidia.com>
Signed-off-by: Arun Shamanna Lakshmi <aruns@nvidia.com>
Reviewed-on: http://git-master/r/397314
(cherry picked from commit 3972fbeccbec85f74a0ed959212382db5471e65d)
Reviewed-on: http://git-master/r/355184
Tested-by: Sumit Bhattacharya <sumitb@nvidia.com>
Reviewed-by: Sumit Bhattacharya <sumitb@nvidia.com>
Reviewed-by: Rahul Mittal <rmittal@nvidia.com>
This commit is contained in:
Songhee Baek
2014-01-02 17:17:53 -08:00
committed by Sameer Pujar
parent 7a66be7f52
commit f9bb9e84e5
12 changed files with 6391 additions and 3 deletions

View File

@@ -1,8 +1,8 @@
config SND_SOC_TEGRA_ALT config SND_SOC_TEGRA_ALT
tristate "Alternative DAPM-based SoC audio support for the Tegra System-on-Chip" 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 REGMAP_MMIO
select SND_SOC_DMAENGINE_PCM if TEGRA20_APB_DMA select SND_SOC_DMAENGINE_PCM
help help
Say Y or M here if you want support for SoC audio on Tegra, using the 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 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 SND_SOC_TEGRA_ALT
depends on ARCH_TEGRA_11x_SOC || ARCH_TEGRA_12x_SOC 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 config SND_SOC_TEGRA30_XBAR_ALT
tristate "Tegra30 XBAR driver" tristate "Tegra30 XBAR driver"
depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER
help help
Say Y or M if you want to add support for Tegra30 XBAR module. 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 config SND_SOC_TEGRA30_APBIF_ALT
tristate "Tegra30 APBIF driver" tristate "Tegra30 APBIF driver"
depends on SND_SOC_TEGRA30_XBAR_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER depends on SND_SOC_TEGRA30_XBAR_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER
help help
Say Y or M if you want to add support for Tegra30 APBIF module. 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 config SND_SOC_TEGRA30_I2S_ALT
tristate "Tegra30 I2S driver" tristate "Tegra30 I2S driver"
depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER
@@ -54,6 +71,54 @@ config SND_SOC_TEGRA114_ADX_ALT
help help
Say Y or M if you want to add support for Tegra114 ADX module. 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 config SND_SOC_TEGRA_VCM30T124_ALT
tristate "SoC Audio support for VCM30_T124" tristate "SoC Audio support for VCM30_T124"
depends on SND_SOC_TEGRA_ALT depends on SND_SOC_TEGRA_ALT

View File

@@ -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-tegra30-alt-spdif-objs := tegra30_spdif_alt.o
snd-soc-tegra114-alt-amx-objs := tegra114_amx_alt.o snd-soc-tegra114-alt-amx-objs := tegra114_amx_alt.o
snd-soc-tegra114-alt-adx-objs := tegra114_adx_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-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_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_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_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_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_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_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 # Tegra machine Support
snd-soc-tegra-alt-vcm30t124-objs := tegra_vcm30t124_alt.o snd-soc-tegra-alt-vcm30t124-objs := tegra_vcm30t124_alt.o

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <mach/clk.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#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 <sbaek@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 ADMAIF driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/of_device.h>
#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);
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);
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 <aruns@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 ADX ASoC driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_DEVICE_TABLE(of, tegra210_adx_of_match);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/pinctrl/consumer.h>
#include <linux/of_device.h>
#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 <aruns@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 AFC ASoC driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_DEVICE_TABLE(of, tegra210_afc_of_match);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/of_device.h>
#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);
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);
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 <sbaek@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 AMX ASoC driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_DEVICE_TABLE(of, tegra210_amx_of_match);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/pinctrl/consumer.h>
#include <linux/of_device.h>
#include <linux/debugfs.h>
#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 <sbaek@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 I2S ASoC driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_DEVICE_TABLE(of, tegra210_i2s_of_match);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/of_device.h>
#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 <aruns@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 MIXER ASoC driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_DEVICE_TABLE(of, tegra210_mixer_of_match);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/pinctrl/consumer.h>
#include <linux/of_device.h>
#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 <aruns@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 MVC ASoC driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_DEVICE_TABLE(of, tegra210_mvc_of_match);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/pinctrl/consumer.h>
#include <linux/of_device.h>
#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 <aruns@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 SFC ASoC driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_DEVICE_TABLE(of, tegra210_sfc_of_match);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/of_device.h>
#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 <aruns@nvidia.com>");
MODULE_AUTHOR("Songhee Baek <sbaek@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 SPDIF ASoC driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_DEVICE_TABLE(of, tegra210_spdif_of_match);

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/soc.h>
#include <mach/clk.h>
#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 <swarren@nvidia.com>");
MODULE_DESCRIPTION("Tegra210 XBAR driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);