diff options
-rw-r--r-- | sound/soc/Kconfig | 1 | ||||
-rw-r--r-- | sound/soc/Makefile | 1 | ||||
-rw-r--r-- | sound/soc/tegra-alt/Kconfig | 49 | ||||
-rw-r--r-- | sound/soc/tegra-alt/Makefile | 20 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra114_adx_alt.c | 575 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra114_adx_alt.h | 112 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra114_amx_alt.c | 601 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra114_amx_alt.h | 127 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra30_apbif_alt.c | 763 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra30_apbif_alt.h | 323 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra30_i2s_alt.c | 661 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra30_i2s_alt.h | 266 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra30_xbar_alt.c | 798 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra30_xbar_alt.h | 171 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra_asoc_utils_alt.c | 441 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra_asoc_utils_alt.h | 75 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra_pcm_alt.c | 282 | ||||
-rw-r--r-- | sound/soc/tegra-alt/tegra_pcm_alt.h | 52 |
18 files changed, 5318 insertions, 0 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 9e675c76436c..423e06d73c5f 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -52,6 +52,7 @@ source "sound/soc/samsung/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/tegra/Kconfig" +source "sound/soc/tegra-alt/Kconfig" source "sound/soc/txx9/Kconfig" source "sound/soc/ux500/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 197b6ae54c8d..3d14cb82795d 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -30,5 +30,6 @@ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += tegra/ +obj-$(CONFIG_SND_SOC) += tegra-alt/ obj-$(CONFIG_SND_SOC) += txx9/ obj-$(CONFIG_SND_SOC) += ux500/ diff --git a/sound/soc/tegra-alt/Kconfig b/sound/soc/tegra-alt/Kconfig new file mode 100644 index 000000000000..60ea06882af9 --- /dev/null +++ b/sound/soc/tegra-alt/Kconfig @@ -0,0 +1,49 @@ +config SND_SOC_TEGRA_ALT + tristate "Alternative DAPM-based SoC audio support for the Tegra System-on-Chip" + depends on ARCH_TEGRA && TEGRA20_APB_DMA + select REGMAP_MMIO + select SND_SOC_DMAENGINE_PCM if TEGRA20_APB_DMA + help + Say Y or M here if you want support for SoC audio on Tegra, using the + alternative driver that exposes to user-space the full routing capabilities + of the AHUB (Audio HUB) hardware module. + +config SND_SOC_TEGRA_ALT_30_OR_LATER + def_bool y + depends on SND_SOC_TEGRA_ALT + depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_11x_SOC || ARCH_TEGRA_12x_SOC + +config SND_SOC_TEGRA_ALT_114_OR_LATER + def_bool y + depends on SND_SOC_TEGRA_ALT + depends on ARCH_TEGRA_11x_SOC || ARCH_TEGRA_12x_SOC + +config SND_SOC_TEGRA30_XBAR_ALT + tristate "Tegra30 XBAR driver" + depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER + help + Say Y or M if you want to add support for Tegra30 XBAR module. + +config SND_SOC_TEGRA30_APBIF_ALT + tristate "Tegra30 APBIF driver" + depends on SND_SOC_TEGRA30_XBAR_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER + help + Say Y or M if you want to add support for Tegra30 APBIF module. + +config SND_SOC_TEGRA30_I2S_ALT + tristate "Tegra30 I2S driver" + depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_30_OR_LATER + help + Say Y or M if you want to add support for Tegra30 I2S module. + +config SND_SOC_TEGRA114_AMX_ALT + tristate "Tegra114 AMX driver" + depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_114_OR_LATER + help + Say Y or M if you want to add support for Tegra114 AMX module. + +config SND_SOC_TEGRA114_ADX_ALT + tristate "Tegra114 ADX driver" + depends on SND_SOC_TEGRA_ALT && SND_SOC_TEGRA_ALT_114_OR_LATER + help + Say Y or M if you want to add support for Tegra114 ADX module. diff --git a/sound/soc/tegra-alt/Makefile b/sound/soc/tegra-alt/Makefile new file mode 100644 index 000000000000..38c71d618d38 --- /dev/null +++ b/sound/soc/tegra-alt/Makefile @@ -0,0 +1,20 @@ +GCOV_PROFILE := y + +subdir-ccflags-y := -Werror + +# Tegra platform Support +snd-soc-tegra-alt-pcm-objs := tegra_pcm_alt.o +snd-soc-tegra-alt-utils-objs := tegra_asoc_utils_alt.o +snd-soc-tegra30-alt-apbif-objs := tegra30_apbif_alt.o +snd-soc-tegra30-alt-xbar-objs := tegra30_xbar_alt.o +snd-soc-tegra30-alt-i2s-objs := tegra30_i2s_alt.o +snd-soc-tegra114-alt-amx-objs := tegra114_amx_alt.o +snd-soc-tegra114-alt-adx-objs := tegra114_adx_alt.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_TEGRA30_APBIF_ALT) += snd-soc-tegra30-alt-apbif.o +obj-$(CONFIG_SND_SOC_TEGRA30_XBAR_ALT) += snd-soc-tegra30-alt-xbar.o +obj-$(CONFIG_SND_SOC_TEGRA30_I2S_ALT) += snd-soc-tegra30-alt-i2s.o +obj-$(CONFIG_SND_SOC_TEGRA114_AMX_ALT) += snd-soc-tegra114-alt-amx.o +obj-$(CONFIG_SND_SOC_TEGRA114_ADX_ALT) += snd-soc-tegra114-alt-adx.o diff --git a/sound/soc/tegra-alt/tegra114_adx_alt.c b/sound/soc/tegra-alt/tegra114_adx_alt.c new file mode 100644 index 000000000000..18572b793028 --- /dev/null +++ b/sound/soc/tegra-alt/tegra114_adx_alt.c @@ -0,0 +1,575 @@ +/* + * tegra114_adx_alt.c - Tegra114 ADX driver + * + * Copyright (c) 2013 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 "tegra30_xbar_alt.h" +#include "tegra114_adx_alt.h" + +#define DRV_NAME "tegra114-adx" + +/** + * tegra114_adx_enable_outstream - enable output stream + * @adx: struct of tegra114_adx + * @stream_id: adx output stream id for enabling + */ +static void tegra114_adx_enable_outstream(struct tegra114_adx *adx, + unsigned int stream_id) +{ + int reg; + + reg = TEGRA_ADX_OUT_CH_CTRL; + + regmap_update_bits(adx->regmap, reg, + TEGRA_ADX_OUT_CH_ENABLE << stream_id, + TEGRA_ADX_OUT_CH_ENABLE << stream_id); +} + +/** + * tegra114_adx_disable_outstream - disable output stream + * @adx: struct of tegra114_adx + * @stream_id: adx output stream id for disabling + */ +static void tegra114_adx_disable_outstream(struct tegra114_adx *adx, + unsigned int stream_id) +{ + int reg; + + reg = TEGRA_ADX_OUT_CH_CTRL; + + regmap_update_bits(adx->regmap, reg, + TEGRA_ADX_OUT_CH_ENABLE << stream_id, + TEGRA_ADX_OUT_CH_DISABLE << stream_id); +} + +/** + * tegra114_adx_set_in_byte_mask - set byte mask for input frame + * @adx: struct of tegra114_adx + * @mask1: enable for bytes 31 ~ 0 of input frame + * @mask2: enable for bytes 63 ~ 32 of input frame + */ +static void tegra114_adx_set_in_byte_mask(struct tegra114_adx *adx, + unsigned int mask1, + unsigned int mask2) +{ + regmap_write(adx->regmap, TEGRA_ADX_IN_BYTE_EN0, mask1); + regmap_write(adx->regmap, TEGRA_ADX_IN_BYTE_EN1, mask2); +} + +/** + * tegra114_adx_set_map_table - set map table not RAM + * @adx: struct of tegra114_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 tegra114_adx_set_map_table(struct tegra114_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 << + TEGRA_ADX_MAP_STREAM_NUMBER_SHIFT) | + (nth_word << + TEGRA_ADX_MAP_WORD_NUMBER_SHIFT) | + (nth_byte << + TEGRA_ADX_MAP_BYTE_NUMBER_SHIFT); +} + +/** + * tegra114_adx_write_map_ram - write map information in RAM + * @adx: struct of tegra114_adx + * @addr: n-th word of input stream + * @val : bytes mapping information of the word + */ +static void tegra114_adx_write_map_ram(struct tegra114_adx *adx, + unsigned int addr, + unsigned int val) +{ + unsigned int reg; + + regmap_write(adx->regmap, TEGRA_ADX_AUDIORAMCTL_ADX_CTRL, + (addr << + TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_RAM_ADR_SHIFT)); + + regmap_write(adx->regmap, TEGRA_ADX_AUDIORAMCTL_ADX_DATA, val); + + regmap_read(adx->regmap, TEGRA_ADX_AUDIORAMCTL_ADX_CTRL, ®); + reg |= TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_HW_ADR_EN_ENABLE; + + regmap_write(adx->regmap, TEGRA_ADX_AUDIORAMCTL_ADX_CTRL, reg); + + regmap_read(adx->regmap, TEGRA_ADX_AUDIORAMCTL_ADX_CTRL, ®); + reg |= TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_RW_WRITE; + + regmap_write(adx->regmap, TEGRA_ADX_AUDIORAMCTL_ADX_CTRL, reg); +} + +static void tegra114_adx_update_map_ram(struct tegra114_adx *adx) +{ + int i; + + for (i = 0; i < TEGRA_ADX_RAM_DEPTH; i++) + tegra114_adx_write_map_ram(adx, i, adx->map[i]); +} + +static int tegra114_adx_runtime_suspend(struct device *dev) +{ + struct tegra114_adx *adx = dev_get_drvdata(dev); + + regcache_cache_only(adx->regmap, true); + + clk_disable_unprepare(adx->clk_adx); + + return 0; +} + +static int tegra114_adx_runtime_resume(struct device *dev) +{ + struct tegra114_adx *adx = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(adx->clk_adx); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + regcache_cache_only(adx->regmap, false); + + return 0; +} + +static int tegra114_adx_set_audio_cif(struct tegra114_adx *adx, + struct snd_pcm_hw_params *params, + unsigned int reg) +{ + int channels, audio_bits; + struct tegra30_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 = TEGRA30_AUDIOCIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio_bits = TEGRA30_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.direction = 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 tegra114_adx_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra114_adx *adx = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = tegra114_adx_set_audio_cif(adx, params, + TEGRA_ADX_AUDIOCIF_CH0_CTRL + + (dai->id * TEGRA_ADX_AUDIOCIF_CH_STRIDE)); + + return ret; +} + +static int tegra114_adx_out_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + struct tegra114_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: + tegra114_adx_enable_outstream(adx, dai->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + tegra114_adx_disable_outstream(adx, dai->id); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra114_adx_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra114_adx *adx = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = tegra114_adx_set_audio_cif(adx, params, + TEGRA_ADX_AUDIOCIF_IN_CTRL); + + return ret; +} + +int tegra114_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 tegra114_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; + } + + 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; + tegra114_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; + } + } + + tegra114_adx_update_map_ram(adx); + + tegra114_adx_set_in_byte_mask(adx, byte_mask1, byte_mask2); + + return 0; +} + +static int tegra114_adx_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra114_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 tegra114_adx_in_dai_ops = { + .hw_params = tegra114_adx_in_hw_params, + .set_channel_map = tegra114_adx_set_channel_map, +}; + +static struct snd_soc_dai_ops tegra114_adx_out_dai_ops = { + .hw_params = tegra114_adx_out_hw_params, + .trigger = tegra114_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 = &tegra114_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 tegra114_adx_dais[] = { + OUT_DAI(0), + OUT_DAI(1), + OUT_DAI(2), + OUT_DAI(3), + IN_DAI(IN, &tegra114_adx_in_dai_ops), +}; + +static const struct snd_soc_dapm_widget tegra114_adx_widgets[] = { + SND_SOC_DAPM_AIF_IN("IN", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("OUT0", NULL, 0, TEGRA_ADX_OUT_CH_CTRL, 0, 0), + SND_SOC_DAPM_AIF_OUT("OUT1", NULL, 0, TEGRA_ADX_OUT_CH_CTRL, 1, 0), + SND_SOC_DAPM_AIF_OUT("OUT2", NULL, 0, TEGRA_ADX_OUT_CH_CTRL, 2, 0), + SND_SOC_DAPM_AIF_OUT("OUT3", NULL, 0, TEGRA_ADX_OUT_CH_CTRL, 3, 0), +}; + +static const struct snd_soc_dapm_route tegra114_adx_routes[] = { + { "IN", NULL, "IN Receive" }, + { "OUT0", NULL, "IN" }, + { "OUT1", NULL, "IN" }, + { "OUT2", NULL, "IN" }, + { "OUT3", NULL, "IN" }, + { "OUT0 Transmit", NULL, "OUT0" }, + { "OUT1 Transmit", NULL, "OUT1" }, + { "OUT2 Transmit", NULL, "OUT2" }, + { "OUT3 Transmit", NULL, "OUT3" }, +}; + +static struct snd_soc_codec_driver tegra114_adx_codec = { + .probe = tegra114_adx_codec_probe, + .dapm_widgets = tegra114_adx_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra114_adx_widgets), + .dapm_routes = tegra114_adx_routes, + .num_dapm_routes = ARRAY_SIZE(tegra114_adx_routes), +}; + +static bool tegra114_adx_wr_rd_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA_ADX_CTRL: + case TEGRA_ADX_OUT_CH_CTRL: + case TEGRA_ADX_IN_BYTE_EN0: + case TEGRA_ADX_IN_BYTE_EN1: + case TEGRA_ADX_AUDIORAMCTL_ADX_CTRL: + case TEGRA_ADX_AUDIORAMCTL_ADX_DATA: + case TEGRA_ADX_AUDIOCIF_IN_CTRL: + case TEGRA_ADX_AUDIOCIF_CH0_CTRL: + case TEGRA_ADX_AUDIOCIF_CH1_CTRL: + case TEGRA_ADX_AUDIOCIF_CH2_CTRL: + case TEGRA_ADX_AUDIOCIF_CH3_CTRL: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra114_adx_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA_ADX_AUDIOCIF_CH3_CTRL, + .writeable_reg = tegra114_adx_wr_rd_reg, + .readable_reg = tegra114_adx_wr_rd_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra114_adx_soc_data soc_data_tegra114 = { + .set_audio_cif = tegra30_xbar_set_cif +}; + +static const struct tegra114_adx_soc_data soc_data_tegra124 = { + .set_audio_cif = tegra124_xbar_set_cif +}; + +static const struct of_device_id tegra114_adx_of_match[] = { + { .compatible = "nvidia,tegra114-adx", .data = &soc_data_tegra114 }, + { .compatible = "nvidia,tegra124-adx", .data = &soc_data_tegra124 }, + {}, +}; + +static int tegra114_adx_platform_probe(struct platform_device *pdev) +{ + struct tegra114_adx *adx; + struct resource *mem, *memregion; + void __iomem *regs; + int ret = 0; + const struct of_device_id *match; + struct tegra114_adx_soc_data *soc_data; + + match = of_match_device(tegra114_adx_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra114_adx_soc_data *)match->data; + + adx = devm_kzalloc(&pdev->dev, sizeof(struct tegra114_adx), GFP_KERNEL); + if (!adx) { + dev_err(&pdev->dev, "Can't allocate tegra114_adx\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, adx); + + adx->soc_data = soc_data; + + 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; + } + + 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; + } + + adx->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra114_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); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra114_adx_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra114_adx_codec, + tegra114_adx_dais, + ARRAY_SIZE(tegra114_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)) + tegra114_adx_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: + devm_clk_put(&pdev->dev, adx->clk_adx); +err: + return ret; +} + +static int tegra114_adx_platform_remove(struct platform_device *pdev) +{ + struct tegra114_adx *adx = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra114_adx_runtime_suspend(&pdev->dev); + + devm_clk_put(&pdev->dev, adx->clk_adx); + + return 0; +} + +static const struct dev_pm_ops tegra114_adx_pm_ops = { + SET_RUNTIME_PM_OPS(tegra114_adx_runtime_suspend, + tegra114_adx_runtime_resume, NULL) +}; + +static struct platform_driver tegra114_adx_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra114_adx_of_match, + .pm = &tegra114_adx_pm_ops, + }, + .probe = tegra114_adx_platform_probe, + .remove = tegra114_adx_platform_remove, +}; +module_platform_driver(tegra114_adx_driver); + +MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>"); +MODULE_DESCRIPTION("Tegra114 ADX ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra114_adx_of_match); diff --git a/sound/soc/tegra-alt/tegra114_adx_alt.h b/sound/soc/tegra-alt/tegra114_adx_alt.h new file mode 100644 index 000000000000..ea6ba0be2ce1 --- /dev/null +++ b/sound/soc/tegra-alt/tegra114_adx_alt.h @@ -0,0 +1,112 @@ +/* + * tegra114_adx_alt.h - Definitions for Tegra114 ADX driver + * + * Copyright (c) 2013, 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/>. + */ + +#ifndef __TEGRA114_ADX_ALT_H__ +#define __TEGRA114_ADX_ALT_H__ + +#define TEGRA_ADX_AUDIOCIF_CH_STRIDE 4 + +/* Register offsets from ADX*_BASE */ +#define TEGRA_ADX_CTRL 0x00 +#define TEGRA_ADX_OUT_CH_CTRL 0x04 +#define TEGRA_ADX_IN_BYTE_EN0 0x08 +#define TEGRA_ADX_IN_BYTE_EN1 0x0c +#define TEGRA_ADX_AUDIORAMCTL_ADX_CTRL 0x10 +#define TEGRA_ADX_AUDIORAMCTL_ADX_DATA 0x14 +#define TEGRA_ADX_AUDIOCIF_IN_CTRL 0x18 +#define TEGRA_ADX_AUDIOCIF_CH0_CTRL 0x1c +#define TEGRA_ADX_AUDIOCIF_CH1_CTRL 0x20 +#define TEGRA_ADX_AUDIOCIF_CH2_CTRL 0x24 +#define TEGRA_ADX_AUDIOCIF_CH3_CTRL 0x28 + +/* Fields inTEGRA_ADX_CTRL */ +#define TEGRA_ADX_CTRL_SOFT_RESET_SHIFT 31 +#define TEGRA_ADX_CTRL_CG_EN_SHIFT 30 + +/* Fields inTEGRA_ADX_OUT_CH_CTRL */ +#define TEGRA_ADX_OUT_CH_ENABLE 1 +#define TEGRA_ADX_OUT_CH_DISABLE 0 +#define TEGRA_ADX_OUT_CH_CTRL_CH3_FORCE_DISABLE_SHIFT 11 +#define TEGRA_ADX_OUT_CH_CTRL_CH2_FORCE_DISABLE_SHIFT 10 +#define TEGRA_ADX_OUT_CH_CTRL_CH1_FORCE_DISABLE_SHIFT 9 +#define TEGRA_ADX_OUT_CH_CTRL_CH0_FORCE_DISABLE_SHIFT 8 +#define TEGRA_ADX_OUT_CH_CTRL_CH3_DISABLE_SHIFT 3 +#define TEGRA_ADX_OUT_CH_CTRL_CH2_DISABLE_SHIFT 2 +#define TEGRA_ADX_OUT_CH_CTRL_CH1_DISABLE_SHIFT 1 +#define TEGRA_ADX_OUT_CH_CTRL_CH0_DISABLE_SHIFT 0 + +/* Fields inTEGRA_ADX_AUDIORAMCTL_ADX_CTRL */ +#define TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_RAM_ADR_SHIFT 0 +#define TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_HW_ADR_EN_SHIFT 12 +#define TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_RESET_HW_ADR_SHIFT 13 +#define TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_RW_SHIFT 14 +#define TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_READ_BUSY_SHIFT 31 + +#define TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_HW_ADR_EN_ENABLE (1 << TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_HW_ADR_EN_SHIFT) +#define TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_HW_ADR_EN_DISABLE 0 +#define TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_RW_READ 0 +#define TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_RW_WRITE (1 << TEGRA_ADX_AUDIORAMCTL_ADX_CTRL_RW_SHIFT) + +/* + * Those defines are not in register field. + */ +#define TEGRA_ADX_RAM_DEPTH 16 +#define TEGRA_ADX_MAP_STREAM_NUMBER_SHIFT 6 +#define TEGRA_ADX_MAP_WORD_NUMBER_SHIFT 2 +#define TEGRA_ADX_MAP_BYTE_NUMBER_SHIFT 0 + +/* Fields in TEGRA_ADX_AUDIOCIF_IN_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +/* Fields in TEGRA_ADX_AUDIOCIF_CH0_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +/* Fields in TEGRA_ADX_AUDIOCIF_CH1_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +/* Fields in TEGRA_ADX_AUDIOCIF_CH2_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +/* Fields in TEGRA_ADX_AUDIOCIF_CH3_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +enum { + /* Code assumes that OUT_STREAM values of ADX start at 0 */ + /* OUT_STREAM# is equilvant to hw OUT_CH# */ + TEGRA_ADX_OUT_STREAM0 = 0, + TEGRA_ADX_OUT_STREAM1, + TEGRA_ADX_OUT_STREAM2, + TEGRA_ADX_OUT_STREAM3, + TEGRA_ADX_IN_STREAM, + TEGRA_ADX_TOTAL_STREAM +}; + +struct tegra114_adx_soc_data { + void (*set_audio_cif)(struct regmap *map, + unsigned int reg, + struct tegra30_xbar_cif_conf *conf); +}; + +struct tegra114_adx { + struct clk *clk_adx; + struct regmap *regmap; + unsigned int map[16]; + const struct tegra114_adx_soc_data *soc_data; +}; + +#endif diff --git a/sound/soc/tegra-alt/tegra114_amx_alt.c b/sound/soc/tegra-alt/tegra114_amx_alt.c new file mode 100644 index 000000000000..6c6dde7f8e01 --- /dev/null +++ b/sound/soc/tegra-alt/tegra114_amx_alt.c @@ -0,0 +1,601 @@ +/* + * tegra114_amx_alt.c - Tegra114 AMX driver + * + * Copyright (c) 2013 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 "tegra30_xbar_alt.h" +#include "tegra114_amx_alt.h" + +#define DRV_NAME "tegra114_amx" + +/** + * tegra114_amx_set_master_stream - set master stream and dependency + * @amx: struct of tegra114_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, 2 - wait on master + * + * This dependency matter on starting point not every frame. + * Once amx starts to run, it is work as wait on all. + */ +static void tegra114_amx_set_master_stream(struct tegra114_amx *amx, + unsigned int stream_id, + unsigned int dependency) +{ + unsigned int val; + + regmap_read(amx->regmap, TEGRA_AMX_CTRL, &val); + + val &= ~(TEGRA_AMX_CTRL_MSTR_CH_NUM_MASK | TEGRA_AMX_CTRL_CH_DEP_MASK); + val |= ((stream_id << TEGRA_AMX_CTRL_MSTR_CH_NUM_SHIFT) | + (dependency << TEGRA_AMX_CTRL_CH_DEP_SHIFT)); + + regmap_write(amx->regmap, TEGRA_AMX_CTRL, val); +} + +/** + * tegra114_amx_enable_instream - enable input stream + * @amx: struct of tegra114_amx + * @stream_id: amx input stream id for enabling + */ +static void tegra114_amx_enable_instream(struct tegra114_amx *amx, + unsigned int stream_id) +{ + int reg; + + reg = TEGRA_AMX_IN_CH_CTRL; + + regmap_update_bits(amx->regmap, reg, + TEGRA_AMX_IN_CH_ENABLE << stream_id, + TEGRA_AMX_IN_CH_ENABLE << stream_id); +} + +/** + * tegra114_amx_disable_instream - disable input stream + * @amx: struct of tegra114_amx + * @stream_id: amx input stream id for disabling + */ +static void tegra114_amx_disable_instream(struct tegra114_amx *amx, + unsigned int stream_id) +{ + int reg; + + reg = TEGRA_AMX_IN_CH_CTRL; + + regmap_update_bits(amx->regmap, reg, + TEGRA_AMX_IN_CH_ENABLE << stream_id, + TEGRA_AMX_IN_CH_DISABLE << stream_id); +} + +/** + * tegra114_amx_set_out_byte_mask - set byte mask for output frame + * @amx: struct of tegra114_amx + * @mask1: enable for bytes 31 ~ 0 + * @mask2: enable for bytes 63 ~ 32 + */ +static void tegra114_amx_set_out_byte_mask(struct tegra114_amx *amx, + unsigned int mask1, + unsigned int mask2) +{ + regmap_write(amx->regmap, TEGRA_AMX_OUT_BYTE_EN0, mask1); + regmap_write(amx->regmap, TEGRA_AMX_OUT_BYTE_EN1, mask2); +} + +/** + * tegra114_amx_set_map_table - set map table not RAM + * @amx: struct of tegra114_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 tegra114_amx_set_map_table(struct tegra114_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 << TEGRA_AMX_MAP_STREAM_NUMBER_SHIFT) | + (nth_word << TEGRA_AMX_MAP_WORD_NUMBER_SHIFT) | + (nth_byte << TEGRA_AMX_MAP_BYTE_NUMBER_SHIFT); +} + +/** + * tegra114_amx_write_map_ram - write map information in RAM + * @amx: struct of tegra114_amx + * @addr: n-th word of input stream + * @val : bytes mapping information of the word + */ +static void tegra114_amx_write_map_ram(struct tegra114_amx *amx, + unsigned int addr, + unsigned int val) +{ + unsigned int reg; + + regmap_write(amx->regmap, TEGRA_AMX_AUDIORAMCTL_AMX_CTRL, + (addr << TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_RAM_ADR_SHIFT)); + + regmap_write(amx->regmap, TEGRA_AMX_AUDIORAMCTL_AMX_DATA, val); + + regmap_read(amx->regmap, TEGRA_AMX_AUDIORAMCTL_AMX_CTRL, ®); + reg |= TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_HW_ADR_EN_ENABLE; + + regmap_write(amx->regmap, TEGRA_AMX_AUDIORAMCTL_AMX_CTRL, reg); + + regmap_read(amx->regmap, TEGRA_AMX_AUDIORAMCTL_AMX_CTRL, ®); + reg |= TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_RW_WRITE; + + regmap_write(amx->regmap, TEGRA_AMX_AUDIORAMCTL_AMX_CTRL, reg); +} + +static void tegra114_amx_update_map_ram(struct tegra114_amx *amx) +{ + int i; + + for (i = 0; i < TEGRA_AMX_RAM_DEPTH; i++) + tegra114_amx_write_map_ram(amx, i, amx->map[i]); +} + +static int tegra114_amx_runtime_suspend(struct device *dev) +{ + struct tegra114_amx *amx = dev_get_drvdata(dev); + + regcache_cache_only(amx->regmap, true); + + clk_disable_unprepare(amx->clk_amx); + + return 0; +} + +static int tegra114_amx_runtime_resume(struct device *dev) +{ + struct tegra114_amx *amx = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(amx->clk_amx); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + regcache_cache_only(amx->regmap, false); + + return 0; +} + +static int tegra114_amx_set_audio_cif(struct tegra114_amx *amx, + struct snd_pcm_hw_params *params, + unsigned int reg) +{ + int channels, audio_bits; + struct tegra30_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 = TEGRA30_AUDIOCIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio_bits = TEGRA30_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.direction = 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 tegra114_amx_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra114_amx *amx = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = tegra114_amx_set_audio_cif(amx, params, + TEGRA_AMX_AUDIOCIF_CH0_CTRL + + (dai->id * TEGRA_AMX_AUDIOCIF_CH_STRIDE)); + + return ret; +} + +static int tegra114_amx_in_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + struct tegra114_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: + tegra114_amx_enable_instream(amx, dai->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + tegra114_amx_disable_instream(amx, dai->id); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra114_amx_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra114_amx *amx = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = tegra114_amx_set_audio_cif(amx, params, + TEGRA_AMX_AUDIOCIF_OUT_CTRL); + + return ret; +} + +int tegra114_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 tegra114_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; + } + + tegra114_amx_set_master_stream(amx, 0, + TEGRA_AMX_WAIT_ON_ANY); + + 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; + tegra114_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; + } + } + + tegra114_amx_update_map_ram(amx); + + tegra114_amx_set_out_byte_mask(amx, byte_mask1, byte_mask2); + + return 0; +} + +static int tegra114_amx_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra114_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 tegra114_amx_out_dai_ops = { + .hw_params = tegra114_amx_out_hw_params, + .set_channel_map = tegra114_amx_set_channel_map, +}; + +static struct snd_soc_dai_ops tegra114_amx_in_dai_ops = { + .hw_params = tegra114_amx_in_hw_params, + .trigger = tegra114_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 = &tegra114_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 tegra114_amx_dais[] = { + IN_DAI(0), + IN_DAI(1), + IN_DAI(2), + IN_DAI(3), + OUT_DAI(OUT, &tegra114_amx_out_dai_ops), +}; + +static const struct snd_soc_dapm_widget tegra114_amx_widgets[] = { + SND_SOC_DAPM_AIF_IN("IN0", NULL, 0, TEGRA_AMX_IN_CH_CTRL, 0, 0), + SND_SOC_DAPM_AIF_IN("IN1", NULL, 0, TEGRA_AMX_IN_CH_CTRL, 1, 0), + SND_SOC_DAPM_AIF_IN("IN2", NULL, 0, TEGRA_AMX_IN_CH_CTRL, 2, 0), + SND_SOC_DAPM_AIF_IN("IN3", NULL, 0, TEGRA_AMX_IN_CH_CTRL, 3, 0), + SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route tegra114_amx_routes[] = { + { "IN0", NULL, "IN0 Receive" }, + { "IN1", NULL, "IN1 Receive" }, + { "IN2", NULL, "IN2 Receive" }, + { "IN3", NULL, "IN3 Receive" }, + { "OUT", NULL, "IN0" }, + { "OUT", NULL, "IN1" }, + { "OUT", NULL, "IN2" }, + { "OUT", NULL, "IN3" }, + { "OUT Transmit", NULL, "OUT" }, +}; + +static struct snd_soc_codec_driver tegra114_amx_codec = { + .probe = tegra114_amx_codec_probe, + .dapm_widgets = tegra114_amx_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra114_amx_widgets), + .dapm_routes = tegra114_amx_routes, + .num_dapm_routes = ARRAY_SIZE(tegra114_amx_routes), +}; + +static bool tegra114_amx_wr_rd_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA_AMX_CTRL: + case TEGRA_AMX_IN_CH_CTRL: + case TEGRA_AMX_OUT_BYTE_EN0: + case TEGRA_AMX_OUT_BYTE_EN1: + case TEGRA_AMX_AUDIORAMCTL_AMX_CTRL: + case TEGRA_AMX_AUDIORAMCTL_AMX_DATA: + case TEGRA_AMX_AUDIOCIF_OUT_CTRL: + case TEGRA_AMX_AUDIOCIF_CH0_CTRL: + case TEGRA_AMX_AUDIOCIF_CH1_CTRL: + case TEGRA_AMX_AUDIOCIF_CH2_CTRL: + case TEGRA_AMX_AUDIOCIF_CH3_CTRL: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra114_amx_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA_AMX_AUDIOCIF_CH3_CTRL, + .writeable_reg = tegra114_amx_wr_rd_reg, + .readable_reg = tegra114_amx_wr_rd_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra114_amx_soc_data soc_data_tegra114 = { + .set_audio_cif = tegra30_xbar_set_cif +}; + +static const struct tegra114_amx_soc_data soc_data_tegra124 = { + .set_audio_cif = tegra124_xbar_set_cif +}; + +static const struct of_device_id tegra114_amx_of_match[] = { + { .compatible = "nvidia,tegra114-amx", .data = &soc_data_tegra114 }, + { .compatible = "nvidia,tegra124-amx", .data = &soc_data_tegra124 }, + {}, +}; + +static int tegra114_amx_platform_probe(struct platform_device *pdev) +{ + struct tegra114_amx *amx; + struct resource *mem, *memregion; + void __iomem *regs; + int ret; + const struct of_device_id *match; + struct tegra114_amx_soc_data *soc_data; + + match = of_match_device(tegra114_amx_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra114_amx_soc_data *)match->data; + + amx = devm_kzalloc(&pdev->dev, sizeof(struct tegra114_amx), GFP_KERNEL); + if (!amx) { + dev_err(&pdev->dev, "Can't allocate tegra114_amx\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, amx); + + amx->soc_data = soc_data; + + amx->clk_amx = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(amx->clk_amx)) { + dev_err(&pdev->dev, "Can't retrieve tegra114_amx clock\n"); + ret = PTR_ERR(amx->clk_amx); + goto err; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + memregion = devm_request_mem_region(&pdev->dev, mem->start, + resource_size(mem), 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, + &tegra114_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); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra114_amx_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra114_amx_codec, + tegra114_amx_dais, + ARRAY_SIZE(tegra114_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)) + tegra114_amx_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: + devm_clk_put(&pdev->dev, amx->clk_amx); +err: + return ret; +} + +static int tegra114_amx_platform_remove(struct platform_device *pdev) +{ + struct tegra114_amx *amx = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra114_amx_runtime_suspend(&pdev->dev); + + devm_clk_put(&pdev->dev, amx->clk_amx); + + return 0; +} + +static const struct dev_pm_ops tegra114_amx_pm_ops = { + SET_RUNTIME_PM_OPS(tegra114_amx_runtime_suspend, + tegra114_amx_runtime_resume, NULL) +}; + +static struct platform_driver tegra114_amx_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra114_amx_of_match, + .pm = &tegra114_amx_pm_ops, + }, + .probe = tegra114_amx_platform_probe, + .remove = tegra114_amx_platform_remove, +}; +module_platform_driver(tegra114_amx_driver); + +MODULE_AUTHOR("Songhee Baek <sbaek@nvidia.com>"); +MODULE_DESCRIPTION("Tegra114 AMX ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra114_amx_of_match); diff --git a/sound/soc/tegra-alt/tegra114_amx_alt.h b/sound/soc/tegra-alt/tegra114_amx_alt.h new file mode 100644 index 000000000000..25fa4005c1c4 --- /dev/null +++ b/sound/soc/tegra-alt/tegra114_amx_alt.h @@ -0,0 +1,127 @@ +/* + * tegra114_amx_alt.h - Definitions for Tegra114 AMX driver + * + * Copyright (c) 2013, 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 WITHIN + * 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/>. + */ + +#ifndef __TEGRA114_AMX_ALT_H__ +#define __TEGRA114_AMX_ALT_H__ + +#define TEGRA_AMX_AUDIOCIF_CH_STRIDE 4 + +/* Register offsets from TEGRA_AMX*_BASE */ +#define TEGRA_AMX_CTRL 0x00 +#define TEGRA_AMX_IN_CH_CTRL 0x04 +#define TEGRA_AMX_OUT_BYTE_EN0 0x08 +#define TEGRA_AMX_OUT_BYTE_EN1 0x0c +#define TEGRA_AMX_AUDIORAMCTL_AMX_CTRL 0x10 +#define TEGRA_AMX_AUDIORAMCTL_AMX_DATA 0x14 +#define TEGRA_AMX_AUDIOCIF_OUT_CTRL 0x18 +#define TEGRA_AMX_AUDIOCIF_CH0_CTRL 0x1c +#define TEGRA_AMX_AUDIOCIF_CH1_CTRL 0x20 +#define TEGRA_AMX_AUDIOCIF_CH2_CTRL 0x24 +#define TEGRA_AMX_AUDIOCIF_CH3_CTRL 0x28 + +/* Fields in TEGRA_AMX_CTRL */ +#define TEGRA_AMX_CTRL_SOFT_RESET_SHIFT 31 +#define TEGRA_AMX_CTRL_CG_EN_SHIFT 30 + +#define TEGRA_AMX_CTRL_MSTR_CH_NUM_SHIFT 10 +#define TEGRA_AMX_CTRL_MSTR_CH_NUM_MASK (3 << TEGRA_AMX_CTRL_MSTR_CH_NUM_SHIFT) + +#define TEGRA_AMX_CTRL_CH_DEP_SHIFT 8 +#define TEGRA_AMX_CTRL_CH_DEP_MASK (3 << TEGRA_AMX_CTRL_CH_DEP_SHIFT) +#define TEGRA_AMX_CTRL_CH_DEP_WT_ON_ALL 0 +#define TEGRA_AMX_CTRL_CH_DEP_WT_ON_ANY (1 << TEGRA_AMX_CTRL_CH_DEP_SHIFT) +#define TEGRA_AMX_CTRL_CH_DEP_WT_ON_MASTER (2 << TEGRA_AMX_CTRL_CH_DEP_SHIFT) +#define TEGRA_AMX_CTRL_CH_DEP_RSVD (3 << TEGRA_AMX_CTRL_CH_DEP_SHIFT) + +/* Fields in TEGRA_AMX_IN_CH_CTRL */ +#define TEGRA_AMX_IN_CH_ENABLE 1 +#define TEGRA_AMX_IN_CH_DISABLE 0 +#define TEGRA_AMX_IN_CH_CTRL_CH3_FORCE_DISABLE_SHIFT 11 +#define TEGRA_AMX_IN_CH_CTRL_CH2_FORCE_DISABLE_SHIFT 10 +#define TEGRA_AMX_IN_CH_CTRL_CH1_FORCE_DISABLE_SHIFT 9 +#define TEGRA_AMX_IN_CH_CTRL_CH0_FORCE_DISABLE_SHIFT 8 +#define TEGRA_AMX_IN_CH_CTRL_CH3_DISABLE_SHIFT 3 +#define TEGRA_AMX_IN_CH_CTRL_CH2_DISABLE_SHIFT 2 +#define TEGRA_AMX_IN_CH_CTRL_CH1_DISABLE_SHIFT 1 +#define TEGRA_AMX_IN_CH_CTRL_CH0_DISABLE_SHIFT 0 + +/* Fields in TEGRA_AMX_AUDIORAMCTL_AMX_CTRL */ +#define TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_RAM_ADR_SHIFT 0 +#define TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_HW_ADR_EN_SHIFT 12 +#define TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_RESET_HW_ADR_SHIFT 13 +#define TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_RW_SHIFT 14 +#define TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_READ_BUSY_SHIFT 31 + +#define TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_HW_ADR_EN_ENABLE (1 << TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_HW_ADR_EN_SHIFT) +#define TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_HW_ADR_EN_DISABLE 0 +#define TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_RW_READ 0 +#define TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_RW_WRITE (1 << TEGRA_AMX_AUDIORAMCTL_AMX_CTRL_RW_SHIFT) + +/* + * Those defines are not in register field. + */ +#define TEGRA_AMX_RAM_DEPTH 16 +#define TEGRA_AMX_MAP_STREAM_NUMBER_SHIFT 6 +#define TEGRA_AMX_MAP_WORD_NUMBER_SHIFT 2 +#define TEGRA_AMX_MAP_BYTE_NUMBER_SHIFT 0 + +/* Fields in TEGRA_AMX_AUDIOCIF_IN_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +/* Fields in TEGRA_AMX_AUDIOCIF_CH0_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +/* Fields in TEGRA_AMX_AUDIOCIF_CH1_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +/* Fields in TEGRA_AMX_AUDIOCIF_CH2_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +/* Fields in TEGRA_AMX_AUDIOCIF_CH3_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +enum { + TEGRA_AMX_WAIT_ON_ALL, + TEGRA_AMX_WAIT_ON_ANY, + TEGRA_AMX_WAIT_ON_MASTER, +}; + +enum { + /* Code assumes that IN_STREAM values of AMX start at 0 */ + TEGRA_AMX_IN_STREAM0 = 0, + TEGRA_AMX_IN_STREAM1, + TEGRA_AMX_IN_STREAM2, + TEGRA_AMX_IN_STREAM3, + TEGRA_AMX_OUT_STREAM, + TEGRA_AMX_TOTAL_STREAM +}; + +struct tegra114_amx_soc_data { + void (*set_audio_cif)(struct regmap *map, + unsigned int reg, + struct tegra30_xbar_cif_conf *conf); +}; + +struct tegra114_amx { + struct clk *clk_amx; + struct regmap *regmap; + unsigned int map[16]; + const struct tegra114_amx_soc_data *soc_data; +}; + +#endif diff --git a/sound/soc/tegra-alt/tegra30_apbif_alt.c b/sound/soc/tegra-alt/tegra30_apbif_alt.c new file mode 100644 index 000000000000..3dbd36a18eb7 --- /dev/null +++ b/sound/soc/tegra-alt/tegra30_apbif_alt.c @@ -0,0 +1,763 @@ +/* + * tegra30_apbif_alt.c - Tegra APBIF driver + * + * Copyright (c) 2011-2013 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 "tegra30_xbar_alt.h" +#include "tegra30_apbif_alt.h" +#include "tegra_pcm_alt.h" + +#define DRV_NAME "tegra30-ahub-apbif" + +#define FIFOS_IN_FIRST_REG_BLOCK 4 + +#define LAST_REG(name) \ + (TEGRA_AHUB_##name + \ + (TEGRA_AHUB_##name##_STRIDE * TEGRA_AHUB_##name##_COUNT) - 4) + +#define REG_IN_ARRAY(reg, name) \ + ((reg >= TEGRA_AHUB_##name) && \ + (reg <= LAST_REG(name) && \ + (!((reg - TEGRA_AHUB_##name) % TEGRA_AHUB_##name##_STRIDE)))) + +static bool tegra30_apbif_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA_AHUB_CONFIG_LINK_CTRL: + case TEGRA_AHUB_MISC_CTRL: + case TEGRA_AHUB_APBDMA_LIVE_STATUS: + case TEGRA_AHUB_I2S_LIVE_STATUS: + case TEGRA_AHUB_SPDIF_LIVE_STATUS: + case TEGRA_AHUB_I2S_INT_MASK: + case TEGRA_AHUB_DAM_INT_MASK: + case TEGRA_AHUB_SPDIF_INT_MASK: + case TEGRA_AHUB_APBIF_INT_MASK: + case TEGRA_AHUB_I2S_INT_STATUS: + case TEGRA_AHUB_DAM_INT_STATUS: + case TEGRA_AHUB_SPDIF_INT_STATUS: + case TEGRA_AHUB_APBIF_INT_STATUS: + case TEGRA_AHUB_I2S_INT_SOURCE: + case TEGRA_AHUB_DAM_INT_SOURCE: + case TEGRA_AHUB_SPDIF_INT_SOURCE: + case TEGRA_AHUB_APBIF_INT_SOURCE: + case TEGRA_AHUB_I2S_INT_SET: + case TEGRA_AHUB_DAM_INT_SET: + case TEGRA_AHUB_SPDIF_INT_SET: + case TEGRA_AHUB_APBIF_INT_SET: + return true; + default: + break; + }; + + if (REG_IN_ARRAY(reg, CHANNEL_CTRL) || + REG_IN_ARRAY(reg, CHANNEL_CLEAR) || + REG_IN_ARRAY(reg, CHANNEL_STATUS) || + REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || + REG_IN_ARRAY(reg, CHANNEL_RXFIFO) || + REG_IN_ARRAY(reg, CIF_TX_CTRL) || + REG_IN_ARRAY(reg, CIF_RX_CTRL) || + REG_IN_ARRAY(reg, DAM_LIVE_STATUS)) + return true; + + return false; +} + +static bool tegra30_apbif_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA_AHUB_CONFIG_LINK_CTRL: + case TEGRA_AHUB_MISC_CTRL: + case TEGRA_AHUB_APBDMA_LIVE_STATUS: + case TEGRA_AHUB_I2S_LIVE_STATUS: + case TEGRA_AHUB_SPDIF_LIVE_STATUS: + case TEGRA_AHUB_I2S_INT_STATUS: + case TEGRA_AHUB_DAM_INT_STATUS: + case TEGRA_AHUB_SPDIF_INT_STATUS: + case TEGRA_AHUB_APBIF_INT_STATUS: + case TEGRA_AHUB_I2S_INT_SET: + case TEGRA_AHUB_DAM_INT_SET: + case TEGRA_AHUB_SPDIF_INT_SET: + case TEGRA_AHUB_APBIF_INT_SET: + return true; + default: + break; + }; + + if (REG_IN_ARRAY(reg, CHANNEL_CLEAR) || + REG_IN_ARRAY(reg, CHANNEL_STATUS) || + REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || + REG_IN_ARRAY(reg, CHANNEL_RXFIFO) || + REG_IN_ARRAY(reg, DAM_LIVE_STATUS)) + return true; + + return false; +} + +static bool tegra30_apbif_precious_reg(struct device *dev, unsigned int reg) +{ + if (REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || + REG_IN_ARRAY(reg, CHANNEL_RXFIFO)) + return true; + + return false; +} + +static const struct regmap_config tegra30_apbif_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = TEGRA_AHUB_APBIF_INT_SET, + .writeable_reg = tegra30_apbif_wr_rd_reg, + .readable_reg = tegra30_apbif_wr_rd_reg, + .volatile_reg = tegra30_apbif_volatile_reg, + .precious_reg = tegra30_apbif_precious_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_config tegra30_apbif2_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = TEGRA_AHUB_CIF_RX9_CTRL, + .cache_type = REGCACHE_RBTREE, +}; + +static int tegra30_apbif_runtime_suspend(struct device *dev) +{ + struct tegra30_apbif *apbif = dev_get_drvdata(dev); + + regcache_cache_only(apbif->regmap[0], true); + if (apbif->regmap[1]) + regcache_cache_only(apbif->regmap[1], true); + + clk_disable(apbif->clk); + + return 0; +} + +static int tegra30_apbif_runtime_resume(struct device *dev) +{ + struct tegra30_apbif *apbif = dev_get_drvdata(dev); + int ret; + + ret = clk_enable(apbif->clk); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + regcache_cache_only(apbif->regmap[0], false); + if (apbif->regmap[1]) + regcache_cache_only(apbif->regmap[1], false); + + return 0; +} + +static int tegra30_apbif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct tegra30_apbif *apbif = snd_soc_dai_get_drvdata(dai); + u32 reg, mask, val, base_ch; + struct tegra30_xbar_cif_conf cif_conf; + struct regmap *regmap; + + 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 = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_32; + cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_32; + break; + default: + dev_err(dev, "Wrong format!\n"); + return -EINVAL; + } + + if (dai->id < FIFOS_IN_FIRST_REG_BLOCK) { + base_ch = 0; + regmap = apbif->regmap[0]; + } else { + base_ch = FIFOS_IN_FIRST_REG_BLOCK; + regmap = apbif->regmap[1]; + } + + reg = TEGRA_AHUB_CHANNEL_CTRL + + ((dai->id - base_ch) * TEGRA_AHUB_CHANNEL_CTRL_STRIDE); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mask = TEGRA_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK | + TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_EN | + TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_MASK; + val = (7 << TEGRA_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT) | + TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_EN | + TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_16; + regmap_update_bits(regmap, reg, mask, val); + } else { + mask = TEGRA_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK | + TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_EN | + TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_MASK; + val = (7 << TEGRA_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT) | + TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_EN | + TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_16; + regmap_update_bits(regmap, reg, mask, val); + } + + 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) { + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX; + reg = TEGRA_AHUB_CIF_TX_CTRL + + ((dai->id - base_ch) * TEGRA_AHUB_CIF_TX_CTRL_STRIDE); + + } else { + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX; + reg = TEGRA_AHUB_CIF_RX_CTRL + + ((dai->id - base_ch) * TEGRA_AHUB_CIF_RX_CTRL_STRIDE); + } + apbif->soc_data->set_audio_cif(regmap, reg, &cif_conf); + + return 0; +} + +static void tegra30_apbif_start_playback(struct snd_soc_dai *dai) +{ + struct tegra30_apbif *apbif = snd_soc_dai_get_drvdata(dai); + unsigned int reg, base_ch; + struct regmap *regmap; + + if (dai->id < FIFOS_IN_FIRST_REG_BLOCK) { + base_ch = 0; + regmap = apbif->regmap[0]; + } else { + base_ch = FIFOS_IN_FIRST_REG_BLOCK; + regmap = apbif->regmap[1]; + } + + reg = TEGRA_AHUB_CHANNEL_CTRL + + ((dai->id - base_ch) * TEGRA_AHUB_CHANNEL_CTRL_STRIDE); + regmap_update_bits(regmap, reg, TEGRA_AHUB_CHANNEL_CTRL_TX_EN, + TEGRA_AHUB_CHANNEL_CTRL_TX_EN); +} + +static void tegra30_apbif_stop_playback(struct snd_soc_dai *dai) +{ + struct tegra30_apbif *apbif = snd_soc_dai_get_drvdata(dai); + unsigned int reg, base_ch; + struct regmap *regmap; + + if (dai->id < FIFOS_IN_FIRST_REG_BLOCK) { + base_ch = 0; + regmap = apbif->regmap[0]; + } else { + base_ch = FIFOS_IN_FIRST_REG_BLOCK; + regmap = apbif->regmap[1]; + } + + reg = TEGRA_AHUB_CHANNEL_CTRL + + ((dai->id - base_ch) * TEGRA_AHUB_CHANNEL_CTRL_STRIDE); + regmap_update_bits(regmap, reg, TEGRA_AHUB_CHANNEL_CTRL_TX_EN, 0); +} + +static void tegra30_apbif_start_capture(struct snd_soc_dai *dai) +{ + struct tegra30_apbif *apbif = snd_soc_dai_get_drvdata(dai); + unsigned int reg, base_ch; + struct regmap *regmap; + + if (dai->id < FIFOS_IN_FIRST_REG_BLOCK) { + base_ch = 0; + regmap = apbif->regmap[0]; + } else { + base_ch = FIFOS_IN_FIRST_REG_BLOCK; + regmap = apbif->regmap[1]; + } + + reg = TEGRA_AHUB_CHANNEL_CTRL + + ((dai->id - base_ch) * TEGRA_AHUB_CHANNEL_CTRL_STRIDE); + regmap_update_bits(regmap, reg, TEGRA_AHUB_CHANNEL_CTRL_RX_EN, + TEGRA_AHUB_CHANNEL_CTRL_RX_EN); +} + +static void tegra30_apbif_stop_capture(struct snd_soc_dai *dai) +{ + struct tegra30_apbif *apbif = snd_soc_dai_get_drvdata(dai); + unsigned int reg, base_ch; + struct regmap *regmap; + + if (dai->id < FIFOS_IN_FIRST_REG_BLOCK) { + base_ch = 0; + regmap = apbif->regmap[0]; + } else { + base_ch = FIFOS_IN_FIRST_REG_BLOCK; + regmap = apbif->regmap[1]; + } + + reg = TEGRA_AHUB_CHANNEL_CTRL + + ((dai->id - base_ch) * TEGRA_AHUB_CHANNEL_CTRL_STRIDE); + regmap_update_bits(regmap, reg, TEGRA_AHUB_CHANNEL_CTRL_RX_EN, 0); +} + +static int tegra30_apbif_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) + tegra30_apbif_start_playback(dai); + else + tegra30_apbif_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) + tegra30_apbif_stop_playback(dai); + else + tegra30_apbif_stop_capture(dai); + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops tegra30_apbif_dai_ops = { + .hw_params = tegra30_apbif_hw_params, + .trigger = tegra30_apbif_trigger, +}; + +static int tegra30_apbif_dai_probe(struct snd_soc_dai *dai) +{ + struct tegra30_apbif *apbif = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &apbif->capture_dma_data[dai->id]; + dai->playback_dma_data = &apbif->playback_dma_data[dai->id]; + + return 0; +} + +#define APBIF_DAI(id) \ + { \ + .name = "APBIF" #id, \ + .probe = tegra30_apbif_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 = &tegra30_apbif_dai_ops, \ + } + +static struct snd_soc_dai_driver tegra30_apbif_dais[10] = { + APBIF_DAI(0), + APBIF_DAI(1), + APBIF_DAI(2), + APBIF_DAI(3), + APBIF_DAI(4), + APBIF_DAI(5), + APBIF_DAI(6), + APBIF_DAI(7), + APBIF_DAI(8), + APBIF_DAI(9), +}; + +static const struct snd_soc_component_driver tegra30_apbif_dai_driver = { + .name = DRV_NAME, +}; + +#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; + unsigned int clk_list_mask; +} configlink_clocks[] = { + { "i2s0", CLK_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s1", CLK_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s2", CLK_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s3", CLK_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s4", CLK_LIST_MASK_TEGRA30_OR_LATER }, + { "dam0", CLK_LIST_MASK_TEGRA30_OR_LATER }, + { "dam1", CLK_LIST_MASK_TEGRA30_OR_LATER }, + { "dam2", CLK_LIST_MASK_TEGRA30_OR_LATER }, + { "spdif_in", CLK_LIST_MASK_TEGRA30_OR_LATER }, + { "amx", CLK_LIST_MASK_TEGRA114_OR_LATER }, + { "adx", CLK_LIST_MASK_TEGRA114_OR_LATER }, + { "amx1", CLK_LIST_MASK_TEGRA124 }, + { "adx1", CLK_LIST_MASK_TEGRA124 }, + { "afc0", CLK_LIST_MASK_TEGRA124 }, + { "afc1", CLK_LIST_MASK_TEGRA124 }, + { "afc2", CLK_LIST_MASK_TEGRA124 }, + { "afc3", CLK_LIST_MASK_TEGRA124 }, + { "afc4", CLK_LIST_MASK_TEGRA124 }, + { "afc5", CLK_LIST_MASK_TEGRA124 }, +}; + + +struct of_dev_auxdata tegra30_apbif_auxdata[] = { + OF_DEV_AUXDATA("nvidia,tegra30-i2s", 0x70080300, "tegra30-i2s.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra30-i2s", 0x70080400, "tegra30-i2s.1", NULL), + OF_DEV_AUXDATA("nvidia,tegra30-i2s", 0x70080500, "tegra30-i2s.2", NULL), + OF_DEV_AUXDATA("nvidia,tegra30-i2s", 0x70080600, "tegra30-i2s.3", NULL), + OF_DEV_AUXDATA("nvidia,tegra30-i2s", 0x70080700, "tegra30-i2s.4", NULL), + OF_DEV_AUXDATA("nvidia,tegra114-amx", 0x70080c00, "tegra114-amx.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra114-adx", 0x70080e00, "tegra114-adx.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra124-i2s", 0x70301000, "tegra30-i2s.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra124-i2s", 0x70301100, "tegra30-i2s.1", NULL), + OF_DEV_AUXDATA("nvidia,tegra124-i2s", 0x70301200, "tegra30-i2s.2", NULL), + OF_DEV_AUXDATA("nvidia,tegra124-i2s", 0x70301300, "tegra30-i2s.3", NULL), + OF_DEV_AUXDATA("nvidia,tegra124-i2s", 0x70301400, "tegra30-i2s.4", NULL), + OF_DEV_AUXDATA("nvidia,tegra124-amx", 0x70303000, "tegra124-amx.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra124-amx", 0x70303100, "tegra124-amx.1", NULL), + OF_DEV_AUXDATA("nvidia,tegra124-adx", 0x70303800, "tegra124-adx.0", NULL), + OF_DEV_AUXDATA("nvidia,tegra124-adx", 0x70303900, "tegra124-adx.1", NULL), + {} +}; + +static struct tegra30_apbif_soc_data soc_data_tegra30 = { + .num_ch = FIFOS_IN_FIRST_REG_BLOCK, + .clk_list_mask = CLK_LIST_MASK_TEGRA30, + .set_audio_cif = tegra30_xbar_set_cif, +}; + +static struct tegra30_apbif_soc_data soc_data_tegra114 = { + .num_ch = FIFOS_IN_FIRST_REG_BLOCK + 6, + .clk_list_mask = CLK_LIST_MASK_TEGRA114, + .set_audio_cif = tegra30_xbar_set_cif, +}; + +static struct tegra30_apbif_soc_data soc_data_tegra124 = { + .num_ch = FIFOS_IN_FIRST_REG_BLOCK + 6, + .clk_list_mask = CLK_LIST_MASK_TEGRA124, + .set_audio_cif = tegra124_xbar_set_cif, +}; + +static const struct of_device_id tegra30_apbif_of_match[] = { + { .compatible = "nvidia,tegra30-ahub", .data = &soc_data_tegra30 }, + { .compatible = "nvidia,tegra114-ahub", .data = &soc_data_tegra114 }, + { .compatible = "nvidia,tegra124-ahub", .data = &soc_data_tegra124 }, + {}, +}; + +static struct platform_device_info tegra30_xbar_device_info = { + .name = "tegra30-ahub-xbar", + .id = -1, +}; + +static int tegra30_apbif_probe(struct platform_device *pdev) +{ + int i; + struct clk *clk; + int ret; + struct tegra30_apbif *apbif; + void __iomem *regs; + struct resource *res[2]; + u32 of_dma[10][2]; + const struct of_device_id *match; + struct tegra30_apbif_soc_data *soc_data; + + match = of_match_device(tegra30_apbif_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + return -ENODEV; + } + soc_data = (struct tegra30_apbif_soc_data *)match->data; + + /* + * The TEGRA_AHUB APBIF hosts 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++) { + if (!(configlink_clocks[i].clk_list_mask & + soc_data->clk_list_mask)) + continue; + 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); + } + + apbif = devm_kzalloc(&pdev->dev, sizeof(*apbif), GFP_KERNEL); + if (!apbif) { + dev_err(&pdev->dev, "Can't allocate tegra30_apbif\n"); + ret = -ENOMEM; + goto err; + } + + dev_set_drvdata(&pdev->dev, apbif); + + apbif->soc_data = soc_data; + + apbif->capture_dma_data = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_alt_pcm_dma_params) * + apbif->soc_data->num_ch, + GFP_KERNEL); + if (!apbif->capture_dma_data) { + dev_err(&pdev->dev, "Can't allocate tegra_alt_pcm_dma_params\n"); + ret = -ENOMEM; + goto err; + } + + apbif->playback_dma_data = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_alt_pcm_dma_params) * + apbif->soc_data->num_ch, + GFP_KERNEL); + if (!apbif->playback_dma_data) { + dev_err(&pdev->dev, "Can't allocate tegra_alt_pcm_dma_params\n"); + ret = -ENOMEM; + goto err; + } + + apbif->clk = devm_clk_get(&pdev->dev, "apbif"); + if (IS_ERR(apbif->clk)) { + dev_err(&pdev->dev, "Can't retrieve clock\n"); + ret = PTR_ERR(apbif->clk); + goto err; + } + + res[0] = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res[0]) { + dev_err(&pdev->dev, "No memory resource for apbif\n"); + ret = -ENODEV; + goto err_clk_put; + } + res[1] = NULL; + + regs = devm_request_and_ioremap(&pdev->dev, res[0]); + if (!regs) { + dev_err(&pdev->dev, "request/iomap region failed\n"); + ret = -ENODEV; + goto err_clk_put; + } + + apbif->regmap[0] = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra30_apbif_regmap_config); + if (IS_ERR(apbif->regmap[0])) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(apbif->regmap[0]); + goto err_clk_put; + } + regcache_cache_only(apbif->regmap[0], true); + + if (apbif->soc_data->num_ch > FIFOS_IN_FIRST_REG_BLOCK) { + res[1] = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (!res[1]) { + dev_info(&pdev->dev, "No memory resource for apbif2\n"); + ret = -ENODEV; + goto err_clk_put; + } + + regs = devm_request_and_ioremap(&pdev->dev, res[1]); + if (!regs) { + dev_err(&pdev->dev, "request/iomap region failed\n"); + ret = -ENODEV; + goto err_clk_put; + } + + apbif->regmap[1] = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra30_apbif2_regmap_config); + if (IS_ERR(apbif->regmap[1])) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(apbif->regmap[1]); + goto err_clk_put; + } + regcache_cache_only(apbif->regmap[1], true); + } + + if (of_property_read_u32_array(pdev->dev.of_node, + "nvidia,dma-request-selector", + &of_dma[0][0], + apbif->soc_data->num_ch * 2) < 0) { + dev_err(&pdev->dev, + "Missing property nvidia,dma-request-selector\n"); + ret = -ENODEV; + goto err_clk_put; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra30_apbif_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + /* default DAI number is 4 */ + for (i = 0; i < apbif->soc_data->num_ch; i++) { + if (i < FIFOS_IN_FIRST_REG_BLOCK) { + apbif->playback_dma_data[i].addr = res[0]->start + + TEGRA_AHUB_CHANNEL_TXFIFO + + (i * TEGRA_AHUB_CHANNEL_TXFIFO_STRIDE); + + apbif->capture_dma_data[i].addr = res[0]->start + + TEGRA_AHUB_CHANNEL_RXFIFO + + (i * TEGRA_AHUB_CHANNEL_RXFIFO_STRIDE); + } else { + apbif->playback_dma_data[i].addr = res[1]->start + + TEGRA_AHUB_CHANNEL_TXFIFO + + ((i - FIFOS_IN_FIRST_REG_BLOCK) * + TEGRA_AHUB_CHANNEL_TXFIFO_STRIDE); + + apbif->capture_dma_data[i].addr = res[1]->start + + TEGRA_AHUB_CHANNEL_RXFIFO + + ((i - FIFOS_IN_FIRST_REG_BLOCK) * + TEGRA_AHUB_CHANNEL_RXFIFO_STRIDE); + } + + apbif->playback_dma_data[i].wrap = 4; + apbif->playback_dma_data[i].width = 32; + apbif->playback_dma_data[i].req_sel = of_dma[i][1]; + + apbif->capture_dma_data[i].wrap = 4; + apbif->capture_dma_data[i].width = 32; + apbif->capture_dma_data[i].req_sel = of_dma[i][1]; + } + + + ret = snd_soc_register_component(&pdev->dev, + &tegra30_apbif_dai_driver, + tegra30_apbif_dais, + ARRAY_SIZE(tegra30_apbif_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; + } + + tegra30_xbar_device_info.res = platform_get_resource(pdev, + IORESOURCE_MEM, 1); + if (!tegra30_xbar_device_info.res) { + dev_err(&pdev->dev, "No memory resource for xbar\n"); + goto err_unregister_platform; + } + tegra30_xbar_device_info.num_res = 1; + tegra30_xbar_device_info.parent = &pdev->dev; + platform_device_register_full(&tegra30_xbar_device_info); + + of_platform_populate(pdev->dev.of_node, NULL, tegra30_apbif_auxdata, + &pdev->dev); + + return 0; + +err_unregister_platform: + tegra_alt_pcm_platform_unregister(&pdev->dev); +err_unregister_dais: + snd_soc_unregister_component(&pdev->dev); +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra30_apbif_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: + devm_clk_put(&pdev->dev, apbif->clk); +err: + return ret; +} + +static int tegra30_apbif_remove(struct platform_device *pdev) +{ + struct tegra30_apbif *apbif = 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)) + tegra30_apbif_runtime_suspend(&pdev->dev); + + devm_clk_put(&pdev->dev, apbif->clk); + + return 0; +} + +static const struct dev_pm_ops tegra30_apbif_pm_ops = { + SET_RUNTIME_PM_OPS(tegra30_apbif_runtime_suspend, + tegra30_apbif_runtime_resume, NULL) +}; + +static struct platform_driver tegra30_apbif_driver = { + .probe = tegra30_apbif_probe, + .remove = tegra30_apbif_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra30_apbif_of_match, + .pm = &tegra30_apbif_pm_ops, + }, +}; +module_platform_driver(tegra30_apbif_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra30 APBIF driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra-alt/tegra30_apbif_alt.h b/sound/soc/tegra-alt/tegra30_apbif_alt.h new file mode 100644 index 000000000000..13b189012e84 --- /dev/null +++ b/sound/soc/tegra-alt/tegra30_apbif_alt.h @@ -0,0 +1,323 @@ +/* + * tegra30_apbif_alt.h - Tegra30 APBIF registers + * + * Copyright (c) 2011-2013 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/>. + */ + +#ifndef __TEGRA_APBIF_ALT_H__ +#define __TEGRA_APBIF_ALT_H__ + +#define TEGRA_APBIF_CHANNEL0_STRIDE 0x20 +#define TEGRA_APBIF_CHANNEL0_COUNT 4 + +#define TEGRA_APBIF_AUDIOCIF_STRIDE 0x20 +#define TEGRA_APBIF_AUDIOCIF_COUNT 4 + +/* TEGRA_AHUB_CHANNEL_CTRL */ + +#define TEGRA_AHUB_CHANNEL_CTRL 0x0 +#define TEGRA_AHUB_CHANNEL_CTRL_STRIDE 0x20 +#define TEGRA_AHUB_CHANNEL_CTRL_COUNT 4 +#define TEGRA_AHUB_CHANNEL_CTRL_TX_EN (1 << 31) +#define TEGRA_AHUB_CHANNEL_CTRL_RX_EN (1 << 30) +#define TEGRA_AHUB_CHANNEL_CTRL_LOOPBACK (1 << 29) + +#define TEGRA_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT 16 +#define TEGRA_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US 0xff +#define TEGRA_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK (TEGRA_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US << TEGRA_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT) + +#define TEGRA_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT 8 +#define TEGRA_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US 0xff +#define TEGRA_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK (TEGRA_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US << TEGRA_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT) + +#define TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_EN (1 << 6) + +#define PACK_8_4 2 +#define PACK_16 3 + +#define TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT 4 +#define TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US 3 +#define TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_MASK (TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US << TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) +#define TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_8_4 (PACK_8_4 << TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) +#define TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_16 (PACK_16 << TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) + +#define TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_EN (1 << 2) + +#define TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT 0 +#define TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US 3 +#define TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_MASK (TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US << TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) +#define TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_8_4 (PACK_8_4 << TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) +#define TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_16 (PACK_16 << TEGRA_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) + +/* TEGRA_AHUB_CHANNEL_CLEAR */ + +#define TEGRA_AHUB_CHANNEL_CLEAR 0x4 +#define TEGRA_AHUB_CHANNEL_CLEAR_STRIDE 0x20 +#define TEGRA_AHUB_CHANNEL_CLEAR_COUNT 4 +#define TEGRA_AHUB_CHANNEL_CLEAR_TX_SOFT_RESET (1 << 31) +#define TEGRA_AHUB_CHANNEL_CLEAR_RX_SOFT_RESET (1 << 30) + +/* TEGRA_AHUB_CHANNEL_STATUS */ + +#define TEGRA_AHUB_CHANNEL_STATUS 0x8 +#define TEGRA_AHUB_CHANNEL_STATUS_STRIDE 0x20 +#define TEGRA_AHUB_CHANNEL_STATUS_COUNT 4 +#define TEGRA_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT 24 +#define TEGRA_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US 0xff +#define TEGRA_AHUB_CHANNEL_STATUS_TX_FREE_MASK (TEGRA_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US << TEGRA_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT) +#define TEGRA_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT 16 +#define TEGRA_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US 0xff +#define TEGRA_AHUB_CHANNEL_STATUS_RX_FREE_MASK (TEGRA_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US << TEGRA_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT) +#define TEGRA_AHUB_CHANNEL_STATUS_TX_TRIG (1 << 1) +#define TEGRA_AHUB_CHANNEL_STATUS_RX_TRIG (1 << 0) + +/* TEGRA_AHUB_CHANNEL_TXFIFO */ + +#define TEGRA_AHUB_CHANNEL_TXFIFO 0xc +#define TEGRA_AHUB_CHANNEL_TXFIFO_STRIDE 0x20 +#define TEGRA_AHUB_CHANNEL_TXFIFO_COUNT 4 + +/* TEGRA_AHUB_CHANNEL_RXFIFO */ + +#define TEGRA_AHUB_CHANNEL_RXFIFO 0x10 +#define TEGRA_AHUB_CHANNEL_RXFIFO_STRIDE 0x20 +#define TEGRA_AHUB_CHANNEL_RXFIFO_COUNT 4 + +/* TEGRA_AHUB_CIF_TX_CTRL */ + +#define TEGRA_AHUB_CIF_TX_CTRL 0x14 +#define TEGRA_AHUB_CIF_TX_CTRL_STRIDE 0x20 +#define TEGRA_AHUB_CIF_TX_CTRL_COUNT 4 +/* Uses field from AUDIOCIF_CTRL_* */ + +/* TEGRA_AHUB_CIF_RX_CTRL */ + +#define TEGRA_AHUB_CIF_RX_CTRL 0x18 +#define TEGRA_AHUB_CIF_RX_CTRL_STRIDE 0x20 +#define TEGRA_AHUB_CIF_RX_CTRL_COUNT 4 +/* Uses field from AUDIOCIF_CTRL_* */ + +/* TEGRA_AHUB_CONFIG_LINK_CTRL */ + +#define TEGRA_AHUB_CONFIG_LINK_CTRL 0x80 +#define TEGRA_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT 28 +#define TEGRA_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US 0xf +#define TEGRA_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK (TEGRA_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US << TEGRA_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT) +#define TEGRA_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT 16 +#define TEGRA_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US 0xfff +#define TEGRA_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK (TEGRA_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US << TEGRA_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT) +#define TEGRA_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT 4 +#define TEGRA_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US 0xfff +#define TEGRA_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK (TEGRA_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US << TEGRA_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT) +#define TEGRA_AHUB_CONFIG_LINK_CTRL_CG_EN (1 << 2) +#define TEGRA_AHUB_CONFIG_LINK_CTRL_CLEAR_TIMEOUT_CNTR (1 << 1) +#define TEGRA_AHUB_CONFIG_LINK_CTRL_SOFT_RESET (1 << 0) + +/* TEGRA_AHUB_MISC_CTRL */ + +#define TEGRA_AHUB_MISC_CTRL 0x84 +#define TEGRA_AHUB_MISC_CTRL_AUDIO_ACTIVE (1 << 31) +#define TEGRA_AHUB_MISC_CTRL_AUDIO_CG_EN (1 << 8) +#define TEGRA_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT 0 +#define TEGRA_AHUB_MISC_CTRL_AUDIO_OBS_SEL_MASK (0x1f << TEGRA_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT) + +/* TEGRA_AHUB_APBDMA_LIVE_STATUS */ + +#define TEGRA_AHUB_APBDMA_LIVE_STATUS 0x88 +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_FULL (1 << 31) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_FULL (1 << 30) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_FULL (1 << 29) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_FULL (1 << 28) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_FULL (1 << 27) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_FULL (1 << 26) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_FULL (1 << 25) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_FULL (1 << 24) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_EMPTY (1 << 23) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_EMPTY (1 << 22) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_EMPTY (1 << 21) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_EMPTY (1 << 20) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_EMPTY (1 << 19) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_EMPTY (1 << 18) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_EMPTY (1 << 17) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_EMPTY (1 << 16) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_FULL (1 << 15) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_FULL (1 << 14) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_FULL (1 << 13) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_FULL (1 << 12) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_FULL (1 << 11) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_FULL (1 << 10) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_FULL (1 << 9) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_FULL (1 << 8) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_EMPTY (1 << 7) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_EMPTY (1 << 6) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_EMPTY (1 << 5) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_EMPTY (1 << 4) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_EMPTY (1 << 3) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_EMPTY (1 << 2) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_EMPTY (1 << 1) +#define TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_EMPTY (1 << 0) + +/* TEGRA_AHUB_I2S_LIVE_STATUS */ + +#define TEGRA_AHUB_I2S_LIVE_STATUS 0x8c +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_FULL (1 << 29) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_FULL (1 << 28) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_FULL (1 << 27) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_FULL (1 << 26) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_FULL (1 << 25) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_FULL (1 << 24) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_FULL (1 << 23) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_FULL (1 << 22) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_FULL (1 << 21) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_FULL (1 << 20) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_ENABLED (1 << 19) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_ENABLED (1 << 18) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_ENABLED (1 << 17) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_ENABLED (1 << 16) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_ENABLED (1 << 15) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_ENABLED (1 << 14) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_ENABLED (1 << 13) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_ENABLED (1 << 12) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_ENABLED (1 << 11) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_ENABLED (1 << 10) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_EMPTY (1 << 9) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_EMPTY (1 << 8) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_EMPTY (1 << 7) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_EMPTY (1 << 6) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_EMPTY (1 << 5) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_EMPTY (1 << 4) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_EMPTY (1 << 3) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_EMPTY (1 << 2) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_EMPTY (1 << 1) +#define TEGRA_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_EMPTY (1 << 0) + +/* TEGRA_AHUB_DAM0_LIVE_STATUS */ + +#define TEGRA_AHUB_DAM_LIVE_STATUS 0x90 +#define TEGRA_AHUB_DAM_LIVE_STATUS_STRIDE 0x8 +#define TEGRA_AHUB_DAM_LIVE_STATUS_COUNT 3 +#define TEGRA_AHUB_DAM_LIVE_STATUS_TX_ENABLED (1 << 26) +#define TEGRA_AHUB_DAM_LIVE_STATUS_RX1_ENABLED (1 << 25) +#define TEGRA_AHUB_DAM_LIVE_STATUS_RX0_ENABLED (1 << 24) +#define TEGRA_AHUB_DAM_LIVE_STATUS_TXFIFO_FULL (1 << 15) +#define TEGRA_AHUB_DAM_LIVE_STATUS_RX1FIFO_FULL (1 << 9) +#define TEGRA_AHUB_DAM_LIVE_STATUS_RX0FIFO_FULL (1 << 8) +#define TEGRA_AHUB_DAM_LIVE_STATUS_TXFIFO_EMPTY (1 << 7) +#define TEGRA_AHUB_DAM_LIVE_STATUS_RX1FIFO_EMPTY (1 << 1) +#define TEGRA_AHUB_DAM_LIVE_STATUS_RX0FIFO_EMPTY (1 << 0) + +/* TEGRA_AHUB_SPDIF_LIVE_STATUS */ + +#define TEGRA_AHUB_SPDIF_LIVE_STATUS 0xa8 +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_USER_TX_ENABLED (1 << 11) +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_USER_RX_ENABLED (1 << 10) +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_DATA_TX_ENABLED (1 << 9) +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_DATA_RX_ENABLED (1 << 8) +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_FULL (1 << 7) +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_FULL (1 << 6) +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_FULL (1 << 5) +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_FULL (1 << 4) +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_EMPTY (1 << 3) +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_EMPTY (1 << 2) +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_EMPTY (1 << 1) +#define TEGRA_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_EMPTY (1 << 0) + +/* TEGRA_AHUB_I2S_INT_MASK */ + +#define TEGRA_AHUB_I2S_INT_MASK 0xb0 + +/* TEGRA_AHUB_DAM_INT_MASK */ + +#define TEGRA_AHUB_DAM_INT_MASK 0xb4 + +/* TEGRA_AHUB_SPDIF_INT_MASK */ + +#define TEGRA_AHUB_SPDIF_INT_MASK 0xbc + +/* TEGRA_AHUB_APBIF_INT_MASK */ + +#define TEGRA_AHUB_APBIF_INT_MASK 0xc0 + +/* TEGRA_AHUB_I2S_INT_STATUS */ + +#define TEGRA_AHUB_I2S_INT_STATUS 0xc8 + +/* TEGRA_AHUB_DAM_INT_STATUS */ + +#define TEGRA_AHUB_DAM_INT_STATUS 0xcc + +/* TEGRA_AHUB_SPDIF_INT_STATUS */ + +#define TEGRA_AHUB_SPDIF_INT_STATUS 0xd4 + +/* TEGRA_AHUB_APBIF_INT_STATUS */ + +#define TEGRA_AHUB_APBIF_INT_STATUS 0xd8 + +/* TEGRA_AHUB_I2S_INT_SOURCE */ + +#define TEGRA_AHUB_I2S_INT_SOURCE 0xe0 + +/* TEGRA_AHUB_DAM_INT_SOURCE */ + +#define TEGRA_AHUB_DAM_INT_SOURCE 0xe4 + +/* TEGRA_AHUB_SPDIF_INT_SOURCE */ + +#define TEGRA_AHUB_SPDIF_INT_SOURCE 0xec + +/* TEGRA_AHUB_APBIF_INT_SOURCE */ + +#define TEGRA_AHUB_APBIF_INT_SOURCE 0xf0 + +/* TEGRA_AHUB_I2S_INT_SET */ + +#define TEGRA_AHUB_I2S_INT_SET 0xf8 + +/* TEGRA_AHUB_DAM_INT_SET */ + +#define TEGRA_AHUB_DAM_INT_SET 0xfc + +/* TEGRA_AHUB_SPDIF_INT_SET */ + +#define TEGRA_AHUB_SPDIF_INT_SET 0x100 + +/* TEGRA_AHUB_APBIF_INT_SET */ + +#define TEGRA_AHUB_APBIF_INT_SET 0x104 + +/* TEGRA_AHUB_CIF_RX9_CTRL */ +#define TEGRA_AHUB_CIF_RX9_CTRL 0xb8 + +struct tegra30_apbif_soc_data { + unsigned int num_ch; + unsigned int clk_list_mask; + void (*set_audio_cif)(struct regmap *map, + unsigned int reg, + struct tegra30_xbar_cif_conf *cif_conf); +}; + +struct tegra30_apbif { + struct clk *clk; + /* regmap for APBIF */ + struct regmap *regmap[2]; + /* regmap for APBIF2 */ + struct regmap *regmap1; + struct tegra_alt_pcm_dma_params *capture_dma_data; + struct tegra_alt_pcm_dma_params *playback_dma_data; + const struct tegra30_apbif_soc_data *soc_data; +}; + +#endif diff --git a/sound/soc/tegra-alt/tegra30_i2s_alt.c b/sound/soc/tegra-alt/tegra30_i2s_alt.c new file mode 100644 index 000000000000..6894f5cec0d4 --- /dev/null +++ b/sound/soc/tegra-alt/tegra30_i2s_alt.c @@ -0,0 +1,661 @@ +/* + * tegra30_i2s_alt.c - Tegra30 I2S driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (c) 2010-2013 NVIDIA CORPORATION. All rights reserved. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * 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 "tegra30_xbar_alt.h" +#include "tegra30_i2s_alt.h" + +#define DRV_NAME "tegra30-i2s" + +static void tegra30_i2s_set_slot_ctrl(struct regmap *regmap, + unsigned int total_slots, + unsigned int tx_slot_mask, + unsigned int rx_slot_mask) +{ + regmap_write(regmap, TEGRA30_I2S_SLOT_CTRL, + ((total_slots - 1) << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT) | + (rx_slot_mask << TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT) | + (tx_slot_mask << TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT)); +} + +static void tegra114_i2s_set_slot_ctrl(struct regmap *regmap, + unsigned int total_slots, + unsigned int tx_slot_mask, + unsigned int rx_slot_mask) +{ + regmap_write(regmap, TEGRA30_I2S_SLOT_CTRL, total_slots - 1); + regmap_write(regmap, TEGRA114_I2S_SLOT_CTRL2, + (rx_slot_mask << + TEGRA114_I2S_SLOT_CTRL2_RX_SLOT_ENABLES_SHIFT) | + tx_slot_mask); +} + +static int tegra30_i2s_set_clock_rate(struct device *dev, int clock_rate) +{ + unsigned int val; + struct tegra30_i2s *i2s = dev_get_drvdata(dev); + int ret; + + regmap_read(i2s->regmap, TEGRA30_I2S_CTRL, &val); + + if ((val & TEGRA30_I2S_CTRL_MASTER_MASK) == + TEGRA30_I2S_CTRL_MASTER_ENABLE) { + 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(clk_get_parent(i2s->clk_audio_2x), + i2s->clk_i2s_sync); + if (ret) { + dev_err(dev, "Can't set parent of audio2x clock\n"); + return ret; + } + + ret = clk_set_rate(i2s->clk_audio_2x, clock_rate); + if (ret) { + dev_err(dev, "Can't set audio2x clock rate\n"); + return ret; + } + + ret = clk_set_parent(i2s->clk_i2s, i2s->clk_audio_2x); + if (ret) { + dev_err(dev, "Can't set parent of i2s clock\n"); + return ret; + } + } + + return ret; +} + +static int tegra30_i2s_runtime_suspend(struct device *dev) +{ + struct tegra30_i2s *i2s = dev_get_drvdata(dev); + + regcache_cache_only(i2s->regmap, true); + + clk_disable_unprepare(i2s->clk_i2s); + + return 0; +} + +static int tegra30_i2s_runtime_resume(struct device *dev) +{ + struct tegra30_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 tegra30_i2s_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask, val; + + mask = TEGRA30_I2S_CH_CTRL_EGDE_CTRL_MASK; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val = TEGRA30_I2S_CH_CTRL_EGDE_CTRL_POS_EDGE; + break; + case SND_SOC_DAIFMT_IB_NF: + val = TEGRA30_I2S_CH_CTRL_EGDE_CTRL_NEG_EDGE; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dai->dev); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CH_CTRL, mask, val); + pm_runtime_put(dai->dev); + + mask = TEGRA30_I2S_CTRL_MASTER_MASK; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + val = TEGRA30_I2S_CTRL_SLAVE_ENABLE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + val = TEGRA30_I2S_CTRL_MASTER_ENABLE; + break; + default: + return -EINVAL; + } + + mask |= TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK | + TEGRA30_I2S_CTRL_LRCK_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; + val |= TEGRA30_I2S_LRCK_LEFT_LOW; + break; + case SND_SOC_DAIFMT_DSP_B: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; + val |= TEGRA30_I2S_LRCK_RIGHT_LOW; + break; + case SND_SOC_DAIFMT_I2S: + val |= TEGRA30_I2S_FRAME_FORMAT_LRCK; + val |= TEGRA30_I2S_LRCK_LEFT_LOW; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= TEGRA30_I2S_FRAME_FORMAT_LRCK; + val |= TEGRA30_I2S_LRCK_LEFT_LOW; + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= TEGRA30_I2S_FRAME_FORMAT_LRCK; + val |= TEGRA30_I2S_LRCK_LEFT_LOW; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dai->dev); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val); + pm_runtime_put(dai->dev); + + return 0; +} + +static int tegra30_i2s_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + i2s->srate = freq; + + return 0; +} + +static int tegra30_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 tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask, val, reg; + int ret, sample_size, channels, srate, i2sclock, bitcnt; + struct tegra30_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 = TEGRA30_I2S_CTRL_BIT_SIZE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val = TEGRA30_I2S_CTRL_BIT_SIZE_16; + sample_size = 16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + val = TEGRA30_I2S_CTRL_BIT_SIZE_32; + sample_size = 32; + break; + default: + dev_err(dev, "Wrong format!\n"); + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val); + + srate = i2s->srate; + + regmap_read(i2s->regmap, TEGRA30_I2S_CTRL, &val); + if ((val & TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK) == + TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC) { + i2sclock = srate * channels * sample_size; + i2s->soc_data->set_slot_ctrl(i2s->regmap, channels, + (1 << channels) - 1, + (1 << channels) - 1); + } else + i2sclock = srate * channels * sample_size * 2; + + bitcnt = (i2sclock / srate) - 1; + if ((bitcnt < 0) || + (bitcnt > TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US)) { + dev_err(dev, "Can't set channel bit count\n"); + return -EINVAL; + } + + ret = tegra30_i2s_set_clock_rate(dev, i2sclock); + if (ret) { + dev_err(dev, "Can't set I2S clock rate: %d\n", ret); + return ret; + } + + if (channels != 2) + val = bitcnt << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; + else + val = (bitcnt >> 1) << + TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; + + if (i2sclock % (2 * srate)) + val |= TEGRA30_I2S_TIMING_NON_SYM_ENABLE; + + regmap_write(i2s->regmap, TEGRA30_I2S_TIMING, val); + + cif_conf.threshold = 0; + cif_conf.audio_channels = channels; + cif_conf.client_channels = channels; + cif_conf.audio_bits = (sample_size == 16 ? TEGRA30_AUDIOCIF_BITS_16 : + TEGRA30_AUDIOCIF_BITS_32); + + cif_conf.client_bits = (sample_size == 16 ? TEGRA30_AUDIOCIF_BITS_16 : + TEGRA30_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) { + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX; + reg = TEGRA30_I2S_AUDIOCIF_I2STX_CTRL; + } else { + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX; + reg = TEGRA30_I2S_AUDIOCIF_I2SRX_CTRL; + } + i2s->soc_data->set_audio_cif(i2s->regmap, reg, &cif_conf); + + val = (1 << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) | + (1 << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT); + regmap_write(i2s->regmap, TEGRA30_I2S_OFFSET, val); + + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CH_CTRL, + TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK, + 31 << TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT); + + return 0; +} + +static int tegra30_i2s_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra30_i2s *i2s = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = i2s->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 tegra30_i2s_dai_ops = { + .set_fmt = tegra30_i2s_set_fmt, + .hw_params = tegra30_i2s_hw_params, + .set_sysclk = tegra30_i2s_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver tegra30_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 = &tegra30_i2s_dai_ops, + .symmetric_rates = 1, + } +}; + +static const struct snd_soc_dapm_widget tegra30_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, TEGRA30_I2S_CTRL, + TEGRA30_I2S_CTRL_XFER_EN_RX_SHIFT, 0), + SND_SOC_DAPM_AIF_OUT("DAP TX", NULL, 0, TEGRA30_I2S_CTRL, + TEGRA30_I2S_CTRL_XFER_EN_TX_SHIFT, 0), +}; + +static const struct snd_soc_dapm_route tegra30_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 tegra30_i2s_codec = { + .probe = tegra30_i2s_codec_probe, + .dapm_widgets = tegra30_i2s_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra30_i2s_widgets), + .dapm_routes = tegra30_i2s_routes, + .num_dapm_routes = ARRAY_SIZE(tegra30_i2s_routes), +}; + +static bool tegra30_i2s_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA30_I2S_CTRL: + case TEGRA30_I2S_TIMING: + case TEGRA30_I2S_OFFSET: + case TEGRA30_I2S_CH_CTRL: + case TEGRA30_I2S_SLOT_CTRL: + case TEGRA30_I2S_AUDIOCIF_I2STX_CTRL: + case TEGRA30_I2S_AUDIOCIF_I2SRX_CTRL: + case TEGRA30_I2S_FLOWCTL: + case TEGRA30_I2S_TX_STEP: + case TEGRA30_I2S_FLOW_STATUS: + case TEGRA30_I2S_FLOW_TOTAL: + case TEGRA30_I2S_FLOW_OVER: + case TEGRA30_I2S_FLOW_UNDER: + case TEGRA30_I2S_LCOEF_1_4_0: + case TEGRA30_I2S_LCOEF_1_4_1: + case TEGRA30_I2S_LCOEF_1_4_2: + case TEGRA30_I2S_LCOEF_1_4_3: + case TEGRA30_I2S_LCOEF_1_4_4: + case TEGRA30_I2S_LCOEF_1_4_5: + case TEGRA30_I2S_LCOEF_2_4_0: + case TEGRA30_I2S_LCOEF_2_4_1: + case TEGRA30_I2S_LCOEF_2_4_2: + case TEGRA114_I2S_SLOT_CTRL2: + return true; + default: + return false; + }; +} + +static bool tegra30_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA30_I2S_FLOW_STATUS: + case TEGRA30_I2S_FLOW_TOTAL: + case TEGRA30_I2S_FLOW_OVER: + case TEGRA30_I2S_FLOW_UNDER: + return true; + default: + return false; + }; +} + +static const struct regmap_config tegra30_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA114_I2S_SLOT_CTRL2, + .writeable_reg = tegra30_i2s_wr_rd_reg, + .readable_reg = tegra30_i2s_wr_rd_reg, + .volatile_reg = tegra30_i2s_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct tegra30_i2s_soc_data soc_data_tegra30 = { + .set_audio_cif = tegra30_xbar_set_cif, + .set_slot_ctrl = tegra30_i2s_set_slot_ctrl, +}; + +static const struct tegra30_i2s_soc_data soc_data_tegra114 = { + .set_audio_cif = tegra30_xbar_set_cif, + .set_slot_ctrl = tegra114_i2s_set_slot_ctrl, +}; + +static const struct tegra30_i2s_soc_data soc_data_tegra124 = { + .set_audio_cif = tegra124_xbar_set_cif, + .set_slot_ctrl = tegra114_i2s_set_slot_ctrl, +}; + +static const struct of_device_id tegra30_i2s_of_match[] = { + { .compatible = "nvidia,tegra30-i2s", .data = &soc_data_tegra30 }, + { .compatible = "nvidia,tegra114-i2s", .data = &soc_data_tegra114 }, + { .compatible = "nvidia,tegra124-i2s", .data = &soc_data_tegra124 }, + {}, +}; + +static int tegra30_i2s_platform_probe(struct platform_device *pdev) +{ + struct tegra30_i2s *i2s; + struct resource *mem, *memregion; + void __iomem *regs; + int ret = 0; + const struct of_device_id *match; + struct tegra30_i2s_soc_data *soc_data; + + match = of_match_device(tegra30_i2s_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra30_i2s_soc_data *)match->data; + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_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_audio_2x = devm_clk_get(&pdev->dev, "audio_sync_2x"); + if (IS_ERR(i2s->clk_audio_2x)) { + dev_err(&pdev->dev, "Can't retrieve audio 2x clock\n"); + ret = PTR_ERR(i2s->clk_audio_2x); + goto err_i2s_sync_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_audio_2x_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), DRV_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, + &tegra30_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); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra30_i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_codec(&pdev->dev, &tegra30_i2s_codec, + tegra30_i2s_dais, + ARRAY_SIZE(tegra30_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)) + tegra30_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_audio_2x_clk_put: + devm_clk_put(&pdev->dev, i2s->clk_audio_2x); +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 tegra30_i2s_platform_remove(struct platform_device *pdev) +{ + struct tegra30_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)) + tegra30_i2s_runtime_suspend(&pdev->dev); + + devm_clk_put(&pdev->dev, i2s->clk_i2s); + devm_clk_put(&pdev->dev, i2s->clk_i2s_sync); + devm_clk_put(&pdev->dev, i2s->clk_audio_2x); + clk_put(i2s->clk_pll_a_out0); + + return 0; +} + +static const struct dev_pm_ops tegra30_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(tegra30_i2s_runtime_suspend, + tegra30_i2s_runtime_resume, NULL) +}; + +static struct platform_driver tegra30_i2s_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra30_i2s_of_match, + .pm = &tegra30_i2s_pm_ops, + }, + .probe = tegra30_i2s_platform_probe, + .remove = tegra30_i2s_platform_remove, +}; +module_platform_driver(tegra30_i2s_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra30 I2S ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra30_i2s_of_match); diff --git a/sound/soc/tegra-alt/tegra30_i2s_alt.h b/sound/soc/tegra-alt/tegra30_i2s_alt.h new file mode 100644 index 000000000000..51a572e27481 --- /dev/null +++ b/sound/soc/tegra-alt/tegra30_i2s_alt.h @@ -0,0 +1,266 @@ +/* + * tegra30_i2s_alt.h - Definitions for Tegra30 I2S driver + * + * Copyright (c) 2011-2013, 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/>. + */ + +#ifndef __TEGRA30_I2S_ALT_H__ +#define __TEGRA30_I2S_ALT_H__ + +/* Register offsets from TEGRA30_I2S*_BASE */ + +#define TEGRA30_I2S_CTRL 0x0 +#define TEGRA30_I2S_TIMING 0x4 +#define TEGRA30_I2S_OFFSET 0x08 +#define TEGRA30_I2S_CH_CTRL 0x0c +#define TEGRA30_I2S_SLOT_CTRL 0x10 +#define TEGRA30_I2S_AUDIOCIF_I2STX_CTRL 0x14 +#define TEGRA30_I2S_AUDIOCIF_I2SRX_CTRL 0x18 +#define TEGRA30_I2S_FLOWCTL 0x1c +#define TEGRA30_I2S_TX_STEP 0x20 +#define TEGRA30_I2S_FLOW_STATUS 0x24 +#define TEGRA30_I2S_FLOW_TOTAL 0x28 +#define TEGRA30_I2S_FLOW_OVER 0x2c +#define TEGRA30_I2S_FLOW_UNDER 0x30 +#define TEGRA30_I2S_LCOEF_1_4_0 0x34 +#define TEGRA30_I2S_LCOEF_1_4_1 0x38 +#define TEGRA30_I2S_LCOEF_1_4_2 0x3c +#define TEGRA30_I2S_LCOEF_1_4_3 0x40 +#define TEGRA30_I2S_LCOEF_1_4_4 0x44 +#define TEGRA30_I2S_LCOEF_1_4_5 0x48 +#define TEGRA30_I2S_LCOEF_2_4_0 0x4c +#define TEGRA30_I2S_LCOEF_2_4_1 0x50 +#define TEGRA30_I2S_LCOEF_2_4_2 0x54 +#define TEGRA114_I2S_SLOT_CTRL2 0x64 + +/* Fields in TEGRA30_I2S_CTRL */ +#define TEGRA30_I2S_CTRL_XFER_EN_TX_SHIFT 31 +#define TEGRA30_I2S_CTRL_XFER_EN_RX_SHIFT 30 +#define TEGRA30_I2S_CTRL_XFER_EN_TX (1 << TEGRA30_I2S_CTRL_XFER_EN_TX_SHIFT) +#define TEGRA30_I2S_CTRL_XFER_EN_RX (1 << TEGRA30_I2S_CTRL_XFER_EN_RX_SHIFT) +#define TEGRA30_I2S_CTRL_CG_EN (1 << 29) +#define TEGRA30_I2S_CTRL_SOFT_RESET (1 << 28) +#define TEGRA30_I2S_CTRL_TX_FLOWCTL_EN (1 << 27) + +#define TEGRA30_I2S_CTRL_OBS_SEL_SHIFT 24 +#define TEGRA30_I2S_CTRL_OBS_SEL_MASK (7 << TEGRA30_I2S_CTRL_OBS_SEL_SHIFT) + +#define TEGRA30_I2S_FRAME_FORMAT_LRCK 0 +#define TEGRA30_I2S_FRAME_FORMAT_FSYNC 1 + +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT 12 +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK (7 << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK (TEGRA30_I2S_FRAME_FORMAT_LRCK << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC (TEGRA30_I2S_FRAME_FORMAT_FSYNC << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) + +#define TEGRA30_I2S_CTRL_MASTER_ENABLE (1 << 10) +#define TEGRA30_I2S_CTRL_SLAVE_ENABLE 0 +#define TEGRA30_I2S_CTRL_MASTER_MASK (1 << 10) + +#define TEGRA30_I2S_LRCK_LEFT_LOW 0 +#define TEGRA30_I2S_LRCK_RIGHT_LOW 1 + +#define TEGRA30_I2S_CTRL_LRCK_SHIFT 9 +#define TEGRA30_I2S_CTRL_LRCK_MASK (1 << TEGRA30_I2S_CTRL_LRCK_SHIFT) +#define TEGRA30_I2S_CTRL_LRCK_L_LOW (TEGRA30_I2S_LRCK_LEFT_LOW << TEGRA30_I2S_CTRL_LRCK_SHIFT) +#define TEGRA30_I2S_CTRL_LRCK_R_LOW (TEGRA30_I2S_LRCK_RIGHT_LOW << TEGRA30_I2S_CTRL_LRCK_SHIFT) + +#define TEGRA30_I2S_CTRL_LPBK_ENABLE (1 << 8) + +#define TEGRA30_I2S_BIT_CODE_LINEAR 0 +#define TEGRA30_I2S_BIT_CODE_ULAW 1 +#define TEGRA30_I2S_BIT_CODE_ALAW 2 + +#define TEGRA30_I2S_CTRL_BIT_CODE_SHIFT 4 +#define TEGRA30_I2S_CTRL_BIT_CODE_MASK (3 << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_LINEAR (TEGRA30_I2S_BIT_CODE_LINEAR << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_ULAW (TEGRA30_I2S_BIT_CODE_ULAW << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_ALAW (TEGRA30_I2S_BIT_CODE_ALAW << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) + +#define TEGRA30_I2S_BITS_8 1 +#define TEGRA30_I2S_BITS_12 2 +#define TEGRA30_I2S_BITS_16 3 +#define TEGRA30_I2S_BITS_20 4 +#define TEGRA30_I2S_BITS_24 5 +#define TEGRA30_I2S_BITS_28 6 +#define TEGRA30_I2S_BITS_32 7 + +/* Sample container size; see {RX,TX}_MASK field in CH_CTRL below */ +#define TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT 0 +#define TEGRA30_I2S_CTRL_BIT_SIZE_MASK (7 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_8 (TEGRA30_I2S_BITS_8 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_12 (TEGRA30_I2S_BITS_12 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_16 (TEGRA30_I2S_BITS_16 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_20 (TEGRA30_I2S_BITS_20 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_24 (TEGRA30_I2S_BITS_24 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_28 (TEGRA30_I2S_BITS_28 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_32 (TEGRA30_I2S_BITS_32 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) + +/* Fields in TEGRA30_I2S_TIMING */ + +#define TEGRA30_I2S_TIMING_NON_SYM_ENABLE (1 << 12) +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0 +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7fff +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT) + +/* Fields in TEGRA30_I2S_OFFSET */ + +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT 16 +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US 0x7ff +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK (TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT 0 +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US 0x7ff +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK (TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT) + +/* Fields in TEGRA30_I2S_CH_CTRL */ + +/* (FSYNC width - 1) in bit clocks */ +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT 24 +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US 0xff +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK (TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US << TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT) + +#define TEGRA30_I2S_HIGHZ_NO 0 +#define TEGRA30_I2S_HIGHZ_YES 1 +#define TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK 2 + +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT 12 +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_MASK (3 << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_NO (TEGRA30_I2S_HIGHZ_NO << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_YES (TEGRA30_I2S_HIGHZ_YES << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_ON_HALF_BIT_CLK (TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) + +#define TEGRA30_I2S_MSB_FIRST 0 +#define TEGRA30_I2S_LSB_FIRST 1 + +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT 10 +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MASK (1 << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MSB_FIRST (TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_LSB_FIRST (TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT 9 +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MASK (1 << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MSB_FIRST (TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_LSB_FIRST (TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) + +#define TEGRA30_I2S_POS_EDGE 0 +#define TEGRA30_I2S_NEG_EDGE 1 + +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT 8 +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_MASK (1 << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_POS_EDGE (TEGRA30_I2S_POS_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_NEG_EDGE (TEGRA30_I2S_NEG_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) + +/* Sample size is # bits from BIT_SIZE minus this field */ +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT 4 +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US 7 +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK (TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT) + +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT 0 +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US 7 +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK (TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT) + +/* Fields in TEGRA30_I2S_SLOT_CTRL */ + +/* Number of slots in frame, minus 1 */ +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT 0 +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK_US 7 +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK (TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOT_MASK_US << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOT_SHIFT) +/* Rx Slot enables */ +#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT 8 +#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK_US 0xff +#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK (TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK_US << TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT) + +/* Tx Slot enables */ +#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT 0 +#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK_US 0xff +#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK (TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK_US << TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT) + + + +/* Fields in TEGRA30_I2S_CIF_RX_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +/* Fields in TEGRA30_I2S_CIF_TX_CTRL */ +/* Uses field from AUDIOCIF_CTRL_* in tegra_cif_utils_alt.h */ + +/* Fields in TEGRA30_I2S_FLOWCTL */ + +#define TEGRA30_I2S_FILTER_LINEAR 0 +#define TEGRA30_I2S_FILTER_QUAD 1 + +#define TEGRA30_I2S_FLOWCTL_FILTER_SHIFT 31 +#define TEGRA30_I2S_FLOWCTL_FILTER_MASK (1 << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) +#define TEGRA30_I2S_FLOWCTL_FILTER_LINEAR (TEGRA30_I2S_FILTER_LINEAR << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) +#define TEGRA30_I2S_FLOWCTL_FILTER_QUAD (TEGRA30_I2S_FILTER_QUAD << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) + +/* Fields in TEGRA30_I2S_TX_STEP */ + +#define TEGRA30_I2S_TX_STEP_SHIFT 0 +#define TEGRA30_I2S_TX_STEP_MASK_US 0xffff +#define TEGRA30_I2S_TX_STEP_MASK (TEGRA30_I2S_TX_STEP_MASK_US << TEGRA30_I2S_TX_STEP_SHIFT) + +/* Fields in TEGRA30_I2S_FLOW_STATUS */ + +#define TEGRA30_I2S_FLOW_STATUS_UNDERFLOW (1 << 31) +#define TEGRA30_I2S_FLOW_STATUS_OVERFLOW (1 << 30) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_INT_EN (1 << 4) +#define TEGRA30_I2S_FLOW_STATUS_COUNTER_CLR (1 << 3) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_CLR (1 << 2) +#define TEGRA30_I2S_FLOW_STATUS_COUNTER_EN (1 << 1) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_EN (1 << 0) + +/* + * There are no fields in TEGRA30_I2S_FLOW_TOTAL, I2S_FLOW_OVER, + * TEGRA30_I2S_FLOW_UNDER; they are counters taking the whole register. + */ + +/* Fields in TEGRA30_I2S_LCOEF_* */ + +#define TEGRA30_I2S_LCOEF_COEF_SHIFT 0 +#define TEGRA30_I2S_LCOEF_COEF_MASK_US 0xffff +#define TEGRA30_I2S_LCOEF_COEF_MASK (TEGRA30_I2S_LCOEF_COEF_MASK_US << TEGRA30_I2S_LCOEF_COEF_SHIFT) + +/* Fields in TEGRA114_I2S_SLOT_CTRL2 */ + +/* TDM mode slot enable bitmask */ +#define TEGRA114_I2S_SLOT_CTRL2_RX_SLOT_ENABLES_SHIFT 16 +#define TEGRA114_I2S_SLOT_CTRL2_RX_SLOT_ENABLES_MASK (0xffff << TEGRA114_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT) + +#define TEGRA114_I2S_SLOT_CTRL2_TX_SLOT_ENABLES_SHIFT 0 +#define TEGRA114_I2S_SLOT_CTRL2_TX_SLOT_ENABLES_MASK (0xffff << TEGRA114_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT) + +struct tegra30_i2s_soc_data { + void (*set_audio_cif)(struct regmap *map, + unsigned int reg, + struct tegra30_xbar_cif_conf *conf); + void (*set_slot_ctrl)(struct regmap *map, + unsigned int total_slots, + unsigned int tx_slot_mask, + unsigned int rx_slot_mask); +}; + +struct tegra30_i2s { + struct clk *clk_i2s; + struct clk *clk_i2s_sync; + struct clk *clk_pll_a_out0; + struct clk *clk_audio_2x; + struct regmap *regmap; + struct pinctrl *pinctrl; + struct pinctrl_state *pin_default_state; + struct pinctrl_state *pin_idle_state; + unsigned int srate; + const struct tegra30_i2s_soc_data *soc_data; +}; + +#endif diff --git a/sound/soc/tegra-alt/tegra30_xbar_alt.c b/sound/soc/tegra-alt/tegra30_xbar_alt.c new file mode 100644 index 000000000000..96054fc5d07b --- /dev/null +++ b/sound/soc/tegra-alt/tegra30_xbar_alt.c @@ -0,0 +1,798 @@ +/* + * tegra30_xbar_alt.c - Tegra30 XBAR driver + * + * Copyright (c) 2011-2013 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 "tegra30_xbar_alt.h" + +#define DRV_NAME "tegra30-ahub-xbar" + +static const struct regmap_config tegra30_xbar_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = TEGRA_AHUB_AUDIO_RX_STRIDE * + (TEGRA_AHUB_AUDIO_RX_COUNT - 1), + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_config tegra124_xbar_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = TEGRA_AHUB_AUDIO_RX1 + (TEGRA_AHUB_AUDIO_RX_STRIDE * + (TEGRA_AHUB_AUDIO_RX_COUNT - 1)), + .cache_type = REGCACHE_RBTREE, +}; + +static int tegra30_xbar_runtime_suspend(struct device *dev) +{ + struct tegra30_xbar *xbar = dev_get_drvdata(dev); + + regcache_cache_only(xbar->regmap, true); + + clk_disable(xbar->clk); + + return 0; +} + +static int tegra30_xbar_runtime_resume(struct device *dev) +{ + struct tegra30_xbar *xbar = dev_get_drvdata(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 tegra30_xbar_codec_probe(struct snd_soc_codec *codec) +{ + struct tegra30_xbar *xbar = snd_soc_codec_get_drvdata(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 tegra30_xbar_dais[] = { + DAI(APBIF0), + DAI(APBIF1), + DAI(APBIF2), + DAI(APBIF3), + DAI(I2S0), + DAI(I2S1), + DAI(I2S2), + DAI(I2S3), + DAI(I2S4), + /* index 0..8 above are used on Tegra30 */ + DAI(APBIF4), + DAI(APBIF5), + DAI(APBIF6), + DAI(APBIF7), + DAI(APBIF8), + DAI(APBIF9), + DAI(AMX0), + DAI(AMX0-0), + DAI(AMX0-1), + DAI(AMX0-2), + DAI(AMX0-3), + DAI(ADX0-0), + DAI(ADX0-1), + DAI(ADX0-2), + DAI(ADX0-3), + DAI(ADX0), + /* index 0..24 above are used on Tegra114 */ + DAI(AMX1), + DAI(AMX1-0), + DAI(AMX1-1), + DAI(AMX1-2), + DAI(AMX1-3), + DAI(ADX1-0), + DAI(ADX1-1), + DAI(ADX1-2), + DAI(ADX1-3), + DAI(ADX1), + /* index 0..34 above are used on Tegra124 */ +}; + +static const char * const tegra30_xbar_mux_texts[] = { + "None", + "APBIF0", + "APBIF1", + "APBIF2", + "APBIF3", + "I2S0", + "I2S1", + "I2S2", + "I2S3", + "I2S4", + /* index 0..9 above are used on Tegra30 */ + "APBIF4", + "APBIF5", + "APBIF6", + "APBIF7", + "APBIF8", + "APBIF9", + "AMX0", + "ADX0-0", + "ADX0-1", + "ADX0-2", + "ADX0-3", + /* index 0..20 above are used on Tegra114 */ + "AMX1", + "ADX1-0", + "ADX1-1", + "ADX1-2", + "ADX1-3", + /* index 0..25 above are used on Tegra124 */ +}; + +static const int tegra30_xbar_mux_values[] = { + /* Mux0 input, Mux1 input */ + 0, 0, + BIT(0), 0, + BIT(1), 0, + BIT(2), 0, + BIT(3), 0, + BIT(4), 0, + BIT(5), 0, + BIT(6), 0, + BIT(7), 0, + BIT(8), 0, + /* index 0..9 above are used on Tegra30 */ + BIT(14), 0, + BIT(15), 0, + BIT(16), 0, + BIT(17), 0, + BIT(18), 0, + BIT(19), 0, + BIT(20), 0, + BIT(21), 0, + BIT(22), 0, + BIT(23), 0, + BIT(24), 0, + /* index 0..20 above are used on Tegra114 */ + 0, BIT(0), + 0, BIT(1), + 0, BIT(2), + 0, BIT(3), + 0, BIT(4), + /* index 0..25 above are used on Tegra124 */ +}; + +int tegra30_xbar_get_value_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int reg_val[2], mux; + struct snd_soc_codec *codec = widget->codec; + struct tegra30_xbar *xbar = snd_soc_codec_get_drvdata(codec); + + regmap_read(widget->codec->control_data, e->reg, ®_val[0]); + + if (xbar->soc_data->num_mux1_input) + regmap_read(widget->codec->control_data, e->reg2, ®_val[1]); + else + reg_val[1] = 0; + + for (mux = 0; mux < e->max; mux++) { + if ((reg_val[0] == e->values[mux * 2]) && + (reg_val[1] == e->values[mux * 2 + 1])) + break; + } + ucontrol->value.enumerated.item[0] = mux; + + return 0; +} + +int tegra30_xbar_put_value_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int value[2], mux, old; + struct snd_soc_dapm_update update; + int wi; + struct snd_soc_codec *codec = widget->codec; + struct tegra30_xbar *xbar = snd_soc_codec_get_drvdata(codec); + + if (ucontrol->value.enumerated.item[0] > e->max - 1) + return -EINVAL; + + mux = ucontrol->value.enumerated.item[0]; + value[0] = e->values[ucontrol->value.enumerated.item[0] * 2]; + value[1] = e->values[(ucontrol->value.enumerated.item[0] * 2) + 1]; + + regmap_read(widget->codec->control_data, e->reg, &old); + if (value[0] != old) { + for (wi = 0; wi < wlist->num_widgets; wi++) { + widget = wlist->widgets[wi]; + widget->value = value[0]; + update.kcontrol = kcontrol; + update.widget = widget; + update.reg = e->reg; + update.mask = xbar->soc_data->mask[0]; + update.val = value[0]; + widget->dapm->update = &update; + snd_soc_dapm_mux_update_power(widget, kcontrol, mux, e); + widget->dapm->update = NULL; + } + } + + if (xbar->soc_data->num_mux1_input) { + regmap_read(widget->codec->control_data, e->reg2, &old); + if (value[1] != old) { + for (wi = 0; wi < wlist->num_widgets; wi++) { + widget = wlist->widgets[wi]; + widget->value = value[1]; + update.kcontrol = kcontrol; + update.widget = widget; + update.reg = e->reg2; + update.mask = xbar->soc_data->mask[1]; + update.val = value[1]; + widget->dapm->update = &update; + snd_soc_dapm_mux_update_power(widget, kcontrol, mux, e); + widget->dapm->update = NULL; + } + } + } + + return 0; +} + +#define MUX0_REG(id) (TEGRA_AHUB_AUDIO_RX + \ + (TEGRA_AHUB_AUDIO_RX_STRIDE * (id))) + +#define MUX1_REG(id) (TEGRA_AHUB_AUDIO_RX1 + \ + (TEGRA_AHUB_AUDIO_RX_STRIDE * (id))) + +#define SOC_ENUM_WIDE(xreg, yreg, shift, xmax, xtexts, xvalues) \ +{ .reg = xreg, .reg2 = yreg, .shift_l = shift, .shift_r = shift, \ + .max = xmax, .texts = xtexts, .values = xvalues, \ + .mask = xmax ? roundup_pow_of_two(xmax) - 1 : 0} + +#define SOC_ENUM_WIDE_DECL(name, xreg, yreg, shift, xtexts, xvalues) \ + struct soc_enum name = SOC_ENUM_WIDE(xreg, yreg, shift, \ + ARRAY_SIZE(xtexts), xtexts, xvalues) + +#define MUX_ENUM_CTRL_DECL(ename, id) \ + SOC_ENUM_WIDE_DECL(ename##_enum, MUX0_REG(id), MUX1_REG(id), \ + 0, tegra30_xbar_mux_texts, tegra30_xbar_mux_values); \ + static const struct snd_kcontrol_new ename##_control = \ + SOC_DAPM_ENUM_EXT("Route", ename##_enum,\ + tegra30_xbar_get_value_enum,\ + tegra30_xbar_put_value_enum) + +MUX_ENUM_CTRL_DECL(apbif0_tx, 0x00); +MUX_ENUM_CTRL_DECL(apbif1_tx, 0x01); +MUX_ENUM_CTRL_DECL(apbif2_tx, 0x02); +MUX_ENUM_CTRL_DECL(apbif3_tx, 0x03); +MUX_ENUM_CTRL_DECL(i2s0_tx, 0x04); +MUX_ENUM_CTRL_DECL(i2s1_tx, 0x05); +MUX_ENUM_CTRL_DECL(i2s2_tx, 0x06); +MUX_ENUM_CTRL_DECL(i2s3_tx, 0x07); +MUX_ENUM_CTRL_DECL(i2s4_tx, 0x08); +/* above controls are used on Tegra30 */ +MUX_ENUM_CTRL_DECL(apbif4_tx, 0x11); +MUX_ENUM_CTRL_DECL(apbif5_tx, 0x12); +MUX_ENUM_CTRL_DECL(apbif6_tx, 0x13); +MUX_ENUM_CTRL_DECL(apbif7_tx, 0x14); +MUX_ENUM_CTRL_DECL(apbif8_tx, 0x15); +MUX_ENUM_CTRL_DECL(apbif9_tx, 0x16); +MUX_ENUM_CTRL_DECL(amx00_tx, 0x17); +MUX_ENUM_CTRL_DECL(amx01_tx, 0x18); +MUX_ENUM_CTRL_DECL(amx02_tx, 0x19); +MUX_ENUM_CTRL_DECL(amx03_tx, 0x1a); +MUX_ENUM_CTRL_DECL(adx0_tx, 0x1b); +/* above controls are used on Tegra114 */ +MUX_ENUM_CTRL_DECL(amx10_tx, 0x1e); +MUX_ENUM_CTRL_DECL(amx11_tx, 0x1f); +MUX_ENUM_CTRL_DECL(amx12_tx, 0x20); +MUX_ENUM_CTRL_DECL(amx13_tx, 0x21); +MUX_ENUM_CTRL_DECL(adx1_tx, 0x22); +/* above controls are used on Tegra124 */ + +#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 tegra30_xbar_codec.num_dapm_widgets near the end of + * tegra30_xbar_probe() + */ +static const struct snd_soc_dapm_widget tegra30_xbar_widgets[] = { + WIDGETS("APBIF0", apbif0_tx), + WIDGETS("APBIF1", apbif1_tx), + WIDGETS("APBIF2", apbif2_tx), + WIDGETS("APBIF3", apbif3_tx), + WIDGETS("I2S0", i2s0_tx), + WIDGETS("I2S1", i2s1_tx), + WIDGETS("I2S2", i2s2_tx), + WIDGETS("I2S3", i2s3_tx), + WIDGETS("I2S4", i2s4_tx), + /* index 0..8 above are used on Tegra30 */ + WIDGETS("APBIF4", apbif4_tx), + WIDGETS("APBIF5", apbif5_tx), + WIDGETS("APBIF6", apbif6_tx), + WIDGETS("APBIF7", apbif7_tx), + WIDGETS("APBIF8", apbif8_tx), + WIDGETS("APBIF9", apbif9_tx), + WIDGETS("AMX0-0", amx00_tx), + WIDGETS("AMX0-1", amx01_tx), + WIDGETS("AMX0-2", amx02_tx), + WIDGETS("AMX0-3", amx03_tx), + WIDGETS("ADX0", adx0_tx), + TX_WIDGETS("AMX0"), + TX_WIDGETS("ADX0-0"), + TX_WIDGETS("ADX0-1"), + TX_WIDGETS("ADX0-2"), + TX_WIDGETS("ADX0-3"), + /* index 0..24 above are used on Tegra114 */ + WIDGETS("AMX1-0", amx10_tx), + WIDGETS("AMX1-1", amx11_tx), + WIDGETS("AMX1-2", amx12_tx), + WIDGETS("AMX1-3", amx13_tx), + WIDGETS("ADX1", adx1_tx), + TX_WIDGETS("AMX1"), + TX_WIDGETS("ADX1-0"), + TX_WIDGETS("ADX1-1"), + TX_WIDGETS("ADX1-2"), + TX_WIDGETS("ADX1-3"), + /* index 0..34 above are used on Tegra124 */ +}; + +/* These routes used on Tegra30, Tegra114, Tegra124 */ +#define TEGRA30_ROUTES(name) \ + { name " RX", NULL, name " Receive"}, \ + { name " Transmit", NULL, name " TX"}, \ + { name " TX", NULL, name " Mux" }, \ + { name " Mux", "APBIF0", "APBIF0 RX" }, \ + { name " Mux", "APBIF1", "APBIF1 RX" }, \ + { name " Mux", "APBIF2", "APBIF2 RX" }, \ + { name " Mux", "APBIF3", "APBIF3 RX" }, \ + { name " Mux", "I2S0", "I2S0 RX" }, \ + { name " Mux", "I2S1", "I2S1 RX" }, \ + { name " Mux", "I2S2", "I2S2 RX" }, \ + { name " Mux", "I2S3", "I2S3 RX" }, \ + { name " Mux", "I2S4", "I2S4 RX" }, + +/* These routes used on Tegra114 and Tegra124 */ +#define TEGRA114_ROUTES(name) \ + { name " Mux", "APBIF4", "APBIF4 RX" }, \ + { name " Mux", "APBIF5", "APBIF5 RX" }, \ + { name " Mux", "APBIF6", "APBIF6 RX" }, \ + { name " Mux", "APBIF7", "APBIF7 RX" }, \ + { name " Mux", "APBIF8", "APBIF8 RX" }, \ + { name " Mux", "APBIF9", "APBIF9 RX" }, \ + { name " Mux", "AMX0", "AMX0 RX" }, \ + { name " Mux", "ADX0-0", "ADX0-0 RX" }, \ + { name " Mux", "ADX0-1", "ADX0-1 RX" }, \ + { name " Mux", "ADX0-2", "ADX0-2 RX" }, \ + { name " Mux", "ADX0-3", "ADX0-3 RX" }, + +#define AMX_OUT_ADX_IN_ROUTES(name) \ + { name " RX", NULL, name " Receive"}, \ + { name " Transmit", NULL, name " TX"}, + +/* These routes used on Tegra124 only */ +#define TEGRA124_ROUTES(name) \ + { name " Mux", "AMX1", "AMX1 RX" }, \ + { name " Mux", "ADX1-0", "ADX1-0 RX" }, \ + { name " Mux", "ADX1-1", "ADX1-1 RX" }, \ + { name " Mux", "ADX1-2", "ADX1-2 RX" }, \ + { name " Mux", "ADX1-3", "ADX1-3 RX" }, + +/* + * The number of entries in, and order of, this array is closely tied to the + * calculation of tegra30_xbar_codec.num_dapm_routes near the end of + * tegra30_xbar_probe() + */ +static const struct snd_soc_dapm_route tegra30_xbar_routes[] = { + TEGRA30_ROUTES("APBIF0") + TEGRA30_ROUTES("APBIF1") + TEGRA30_ROUTES("APBIF2") + TEGRA30_ROUTES("APBIF3") + TEGRA30_ROUTES("I2S0") + TEGRA30_ROUTES("I2S1") + TEGRA30_ROUTES("I2S2") + TEGRA30_ROUTES("I2S3") + TEGRA30_ROUTES("I2S4") + /* above routes are used on Tegra30 */ + TEGRA30_ROUTES("APBIF4") + TEGRA30_ROUTES("APBIF5") + TEGRA30_ROUTES("APBIF6") + TEGRA30_ROUTES("APBIF7") + TEGRA30_ROUTES("APBIF8") + TEGRA30_ROUTES("APBIF9") + TEGRA30_ROUTES("AMX0-0") + TEGRA30_ROUTES("AMX0-1") + TEGRA30_ROUTES("AMX0-2") + TEGRA30_ROUTES("AMX0-3") + TEGRA30_ROUTES("ADX0") + TEGRA114_ROUTES("APBIF0") + TEGRA114_ROUTES("APBIF1") + TEGRA114_ROUTES("APBIF2") + TEGRA114_ROUTES("APBIF3") + TEGRA114_ROUTES("I2S0") + TEGRA114_ROUTES("I2S1") + TEGRA114_ROUTES("I2S2") + TEGRA114_ROUTES("I2S3") + TEGRA114_ROUTES("I2S4") + TEGRA114_ROUTES("APBIF4") + TEGRA114_ROUTES("APBIF5") + TEGRA114_ROUTES("APBIF6") + TEGRA114_ROUTES("APBIF7") + TEGRA114_ROUTES("APBIF8") + TEGRA114_ROUTES("APBIF9") + TEGRA114_ROUTES("AMX0-0") + TEGRA114_ROUTES("AMX0-1") + TEGRA114_ROUTES("AMX0-2") + TEGRA114_ROUTES("AMX0-3") + TEGRA114_ROUTES("ADX0") + AMX_OUT_ADX_IN_ROUTES("AMX0") + AMX_OUT_ADX_IN_ROUTES("ADX0-0") + AMX_OUT_ADX_IN_ROUTES("ADX0-1") + AMX_OUT_ADX_IN_ROUTES("ADX0-2") + AMX_OUT_ADX_IN_ROUTES("ADX0-3") + /* above routes are used on Tegra114 */ + TEGRA30_ROUTES("AMX1-0") + TEGRA30_ROUTES("AMX1-1") + TEGRA30_ROUTES("AMX1-2") + TEGRA30_ROUTES("AMX1-3") + TEGRA30_ROUTES("ADX1") + TEGRA114_ROUTES("AMX1-0") + TEGRA114_ROUTES("AMX1-1") + TEGRA114_ROUTES("AMX1-2") + TEGRA114_ROUTES("AMX1-3") + TEGRA114_ROUTES("ADX1") + TEGRA124_ROUTES("APBIF0") + TEGRA124_ROUTES("APBIF1") + TEGRA124_ROUTES("APBIF2") + TEGRA124_ROUTES("APBIF3") + TEGRA124_ROUTES("I2S0") + TEGRA124_ROUTES("I2S1") + TEGRA124_ROUTES("I2S2") + TEGRA124_ROUTES("I2S3") + TEGRA124_ROUTES("I2S4") + TEGRA124_ROUTES("APBIF4") + TEGRA124_ROUTES("APBIF5") + TEGRA124_ROUTES("APBIF6") + TEGRA124_ROUTES("APBIF7") + TEGRA124_ROUTES("APBIF8") + TEGRA124_ROUTES("APBIF9") + TEGRA124_ROUTES("AMX0-0") + TEGRA124_ROUTES("AMX0-1") + TEGRA124_ROUTES("AMX0-2") + TEGRA124_ROUTES("AMX0-3") + TEGRA124_ROUTES("ADX0") + TEGRA124_ROUTES("AMX1-0") + TEGRA124_ROUTES("AMX1-1") + TEGRA124_ROUTES("AMX1-2") + TEGRA124_ROUTES("AMX1-3") + TEGRA124_ROUTES("ADX1") + AMX_OUT_ADX_IN_ROUTES("AMX1") + AMX_OUT_ADX_IN_ROUTES("ADX1-0") + AMX_OUT_ADX_IN_ROUTES("ADX1-1") + AMX_OUT_ADX_IN_ROUTES("ADX1-2") + AMX_OUT_ADX_IN_ROUTES("ADX1-3") + /* above routes are used on Tegra124 */ +}; + +static struct snd_soc_codec_driver tegra30_xbar_codec = { + .probe = tegra30_xbar_codec_probe, + .dapm_widgets = tegra30_xbar_widgets, + .dapm_routes = tegra30_xbar_routes, +}; + +static const struct tegra30_xbar_soc_data soc_data_tegra30 = { + .regmap_config = &tegra30_xbar_regmap_config, + .num_dais = 9, + .num_mux_widgets = 9, + .num_mux0_input = 9, + .num_mux1_input = 0, + .mask[0] = 0x1ff, + .mask[1] = 0, +}; + +static const struct tegra30_xbar_soc_data soc_data_tegra114 = { + .regmap_config = &tegra30_xbar_regmap_config, + .num_dais = 25, + .num_mux_widgets = 20, + .num_mux0_input = 20, + .num_mux1_input = 0, + .mask[0] = 0x1ffffff, + .mask[1] = 0, +}; + +static const struct tegra30_xbar_soc_data soc_data_tegra124 = { + .regmap_config = &tegra124_xbar_regmap_config, + .num_dais = 35, + .num_mux_widgets = 25, + .num_mux0_input = 20, + .num_mux1_input = 5, + .mask[0] = 0x1ffffff, + .mask[1] = 0x7ff, +}; + +static const struct of_device_id tegra30_xbar_of_match[] = { + { .compatible = "nvidia,tegra30-ahub", .data = &soc_data_tegra30 }, + { .compatible = "nvidia,tegra114-ahub", .data = &soc_data_tegra114 }, + { .compatible = "nvidia,tegra124-ahub", .data = &soc_data_tegra124 }, + {}, +}; + +static int tegra30_xbar_probe(struct platform_device *pdev) +{ + struct tegra30_xbar *xbar; + void __iomem *regs; + int ret; + const struct of_device_id *match; + struct tegra30_xbar_soc_data *soc_data; + struct clk *parent_clk; + + match = of_match_device(tegra30_xbar_of_match, pdev->dev.parent); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + soc_data = (struct tegra30_xbar_soc_data *)match->data; + + xbar = devm_kzalloc(&pdev->dev, sizeof(*xbar), GFP_KERNEL); + if (!xbar) { + dev_err(&pdev->dev, "Can't allocate xbar\n"); + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, xbar); + + 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_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 = tegra30_xbar_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + tegra30_xbar_codec.num_dapm_widgets = (soc_data->num_dais * 2) + + soc_data->num_mux_widgets; + + tegra30_xbar_codec.num_dapm_routes = (soc_data->num_dais * 2) + + (soc_data->num_mux_widgets * + (soc_data->num_mux0_input + + soc_data->num_mux1_input + 1)); + + ret = snd_soc_register_codec(&pdev->dev, &tegra30_xbar_codec, + tegra30_xbar_dais, soc_data->num_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)) + tegra30_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 tegra30_xbar_remove(struct platform_device *pdev) +{ + struct tegra30_xbar *xbar = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_codec(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra30_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 tegra30_xbar_pm_ops = { + SET_RUNTIME_PM_OPS(tegra30_xbar_runtime_suspend, + tegra30_xbar_runtime_resume, NULL) +}; + +static struct platform_driver tegra30_xbar_driver = { + .probe = tegra30_xbar_probe, + .remove = tegra30_xbar_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = tegra30_xbar_of_match, + .pm = &tegra30_xbar_pm_ops, + }, +}; +module_platform_driver(tegra30_xbar_driver); + +void tegra30_xbar_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_xbar_cif_conf *conf) +{ + unsigned int value; + + value = (conf->threshold << + TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | + ((conf->audio_channels - 1) << + TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | + ((conf->client_channels - 1) << + TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | + (conf->audio_bits << + TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) | + (conf->client_bits << + TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) | + (conf->expand << + TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) | + (conf->stereo_conv << + TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) | + (conf->replicate << + TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) | + (conf->direction << + TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) | + (conf->truncate << + TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) | + (conf->mono_conv << + TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT); + + regmap_write(regmap, reg, value); +} +EXPORT_SYMBOL_GPL(tegra30_xbar_set_cif); + +void tegra124_xbar_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_xbar_cif_conf *conf) +{ + unsigned int value; + + value = (conf->threshold << + TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | + ((conf->audio_channels - 1) << + TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | + ((conf->client_channels - 1) << + TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | + (conf->audio_bits << + TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) | + (conf->client_bits << + TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) | + (conf->expand << + TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) | + (conf->stereo_conv << + TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) | + (conf->replicate << + TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) | + (conf->direction << + TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) | + (conf->truncate << + TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) | + (conf->mono_conv << + TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT); + + regmap_write(regmap, reg, value); +} +EXPORT_SYMBOL_GPL(tegra124_xbar_set_cif); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra30 XBAR driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra-alt/tegra30_xbar_alt.h b/sound/soc/tegra-alt/tegra30_xbar_alt.h new file mode 100644 index 000000000000..4f99e08884b4 --- /dev/null +++ b/sound/soc/tegra-alt/tegra30_xbar_alt.h @@ -0,0 +1,171 @@ +/* + * tegra30_xbar_alt.h - TEGRA XBAR registers + * + * Copyright (c) 2011-2013 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/>. + */ + +#ifndef __TEGRA_XBAR_ALT_H__ +#define __TEGRA_XBAR_ALT_H__ + +#define TEGRA_AHUB_AUDIO_RX 0x0 +#define TEGRA_AHUB_AUDIO_RX1 0x200 +#define TEGRA_AHUB_AUDIO_RX_STRIDE 0x4 +#define TEGRA_AHUB_AUDIO_RX_COUNT 168 +/* This register repeats twice for each XBAR TX CIF */ +/* The fields in this register are 1 bit per XBAR RX CIF */ + +/* Fields in *_CIF_RX/TX_CTRL; used by AHUB FIFOs, and all other audio modules */ + +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT 28 +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US 0xf +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK (TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) + +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT 24 +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US 0x3f +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK (TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT 24 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US 7 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT 20 +#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US 0xf +#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK (TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT 16 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US 7 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT 16 +#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US 0xf +#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) + +#define TEGRA30_AUDIOCIF_BITS_4 0 +#define TEGRA30_AUDIOCIF_BITS_8 1 +#define TEGRA30_AUDIOCIF_BITS_12 2 +#define TEGRA30_AUDIOCIF_BITS_16 3 +#define TEGRA30_AUDIOCIF_BITS_20 4 +#define TEGRA30_AUDIOCIF_BITS_24 5 +#define TEGRA30_AUDIOCIF_BITS_28 6 +#define TEGRA30_AUDIOCIF_BITS_32 7 + +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT 12 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_MASK (7 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_4 (TEGRA30_AUDIOCIF_BITS_4 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_8 (TEGRA30_AUDIOCIF_BITS_8 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_12 (TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_16 (TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_20 (TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_24 (TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_28 (TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_32 (TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) + +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT 8 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_MASK (7 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_4 (TEGRA30_AUDIOCIF_BITS_4 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_8 (TEGRA30_AUDIOCIF_BITS_8 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_12 (TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_16 (TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_20 (TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_24 (TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_28 (TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_32 (TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) + +#define TEGRA30_AUDIOCIF_EXPAND_ZERO 0 +#define TEGRA30_AUDIOCIF_EXPAND_ONE 1 +#define TEGRA30_AUDIOCIF_EXPAND_LFSR 2 + +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT 6 +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_MASK (3 << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ZERO (TEGRA30_AUDIOCIF_EXPAND_ZERO << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ONE (TEGRA30_AUDIOCIF_EXPAND_ONE << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_LFSR (TEGRA30_AUDIOCIF_EXPAND_LFSR << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) + +#define TEGRA30_AUDIOCIF_STEREO_CONV_CH0 0 +#define TEGRA30_AUDIOCIF_STEREO_CONV_CH1 1 +#define TEGRA30_AUDIOCIF_STEREO_CONV_AVG 2 + +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT 4 +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_MASK (3 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH0 (TEGRA30_AUDIOCIF_STEREO_CONV_CH0 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH1 (TEGRA30_AUDIOCIF_STEREO_CONV_CH1 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_AVG (TEGRA30_AUDIOCIF_STEREO_CONV_AVG << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) + +#define TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT 3 + +#define TEGRA30_AUDIOCIF_DIRECTION_TX 0 +#define TEGRA30_AUDIOCIF_DIRECTION_RX 1 + +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT 2 +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_MASK (1 << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_TX (TEGRA30_AUDIOCIF_DIRECTION_TX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_RX (TEGRA30_AUDIOCIF_DIRECTION_RX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) + +#define TEGRA30_AUDIOCIF_TRUNCATE_ROUND 0 +#define TEGRA30_AUDIOCIF_TRUNCATE_CHOP 1 + +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT 1 +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_MASK (1 << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_ROUND (TEGRA30_AUDIOCIF_TRUNCATE_ROUND << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_CHOP (TEGRA30_AUDIOCIF_TRUNCATE_CHOP << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) + +#define TEGRA30_AUDIOCIF_MONO_CONV_ZERO 0 +#define TEGRA30_AUDIOCIF_MONO_CONV_COPY 1 + +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT 0 +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_MASK (1 << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_ZERO (TEGRA30_AUDIOCIF_MONO_CONV_ZERO << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_COPY (TEGRA30_AUDIOCIF_MONO_CONV_COPY << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) + +struct tegra30_xbar_cif_conf { + unsigned int threshold; + unsigned int audio_channels; + unsigned int client_channels; + unsigned int audio_bits; + unsigned int client_bits; + unsigned int expand; + unsigned int stereo_conv; + unsigned int replicate; + unsigned int direction; + unsigned int truncate; + unsigned int mono_conv; +}; + +void tegra30_xbar_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_xbar_cif_conf *conf); +void tegra124_xbar_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_xbar_cif_conf *conf); + +struct tegra30_xbar_soc_data { + const struct regmap_config *regmap_config; + unsigned int num_dais; + unsigned int num_mux_widgets; + unsigned int num_mux0_input; + unsigned int num_mux1_input; + unsigned int mask[2]; +}; + +struct tegra30_xbar { + struct clk *clk; + struct clk *clk_parent; + struct regmap *regmap; + const struct tegra30_xbar_soc_data *soc_data; +}; + +#endif diff --git a/sound/soc/tegra-alt/tegra_asoc_utils_alt.c b/sound/soc/tegra-alt/tegra_asoc_utils_alt.c new file mode 100644 index 000000000000..08ce9e3ff77a --- /dev/null +++ b/sound/soc/tegra-alt/tegra_asoc_utils_alt.c @@ -0,0 +1,441 @@ +/* + * tegra_asoc_utils_alt.c - MCLK and DAP Utility driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (c) 2010-2013 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <mach/clk.h> +#include <mach/pinmux.h> +#ifdef CONFIG_ARCH_TEGRA_2x_SOC +#include <mach/pinmux-tegra20.h> +#endif +#ifdef CONFIG_ARCH_TEGRA_3x_SOC +#include <mach/pinmux-tegra30.h> +#endif +#ifdef CONFIG_ARCH_TEGRA_11x_SOC +#include <mach/pinmux-t11.h> +#endif +#ifdef CONFIG_ARCH_TEGRA_12x_SOC +#include <mach/pinmux-t12.h> +#endif +#ifdef CONFIG_ARCH_TEGRA_14x_SOC +#include <mach/pinmux-t14.h> +#endif + +#include <sound/soc.h> + +#include "tegra_asoc_utils_alt.h" + +static atomic_t dap_ref_count[5]; + +#define TRISTATE_DAP_PORT(n) \ +static void tristate_dap_##n(bool tristate) \ +{ \ + enum tegra_pingroup fs, sclk, din, dout; \ + fs = TEGRA_PINGROUP_DAP##n##_FS; \ + sclk = TEGRA_PINGROUP_DAP##n##_SCLK; \ + din = TEGRA_PINGROUP_DAP##n##_DIN; \ + dout = TEGRA_PINGROUP_DAP##n##_DOUT; \ + if (tristate) { \ + if (atomic_dec_return(&dap_ref_count[n-1]) == 0) {\ + tegra_pinmux_set_tristate(fs, TEGRA_TRI_TRISTATE); \ + tegra_pinmux_set_tristate(sclk, TEGRA_TRI_TRISTATE); \ + tegra_pinmux_set_tristate(din, TEGRA_TRI_TRISTATE); \ + tegra_pinmux_set_tristate(dout, TEGRA_TRI_TRISTATE); \ + } \ + } else { \ + if (atomic_inc_return(&dap_ref_count[n-1]) == 1) {\ + tegra_pinmux_set_tristate(fs, TEGRA_TRI_NORMAL); \ + tegra_pinmux_set_tristate(sclk, TEGRA_TRI_NORMAL); \ + tegra_pinmux_set_tristate(din, TEGRA_TRI_NORMAL); \ + tegra_pinmux_set_tristate(dout, TEGRA_TRI_NORMAL); \ + } \ + } \ +} + +TRISTATE_DAP_PORT(1) +TRISTATE_DAP_PORT(2) +/*I2S2 and I2S3 for other chips do not map to DAP3 and DAP4 (also +these pinmux dont exist for other chips), they map to some +other pinmux*/ +#if defined(CONFIG_ARCH_TEGRA_11x_SOC)\ + || defined(CONFIG_ARCH_TEGRA_12x_SOC)\ + || defined(CONFIG_ARCH_TEGRA_3x_SOC) + TRISTATE_DAP_PORT(3) + TRISTATE_DAP_PORT(4) +#endif + +int tegra_alt_asoc_utils_tristate_dap(int id, bool tristate) +{ + switch (id) { + case 0: + tristate_dap_1(tristate); + break; + case 1: + tristate_dap_2(tristate); + break; +/*I2S2 and I2S3 for other chips do not map to DAP3 and DAP4 (also +these pinmux dont exist for other chips), they map to some +other pinmux*/ +#if defined(CONFIG_ARCH_TEGRA_11x_SOC)\ + || defined(CONFIG_ARCH_TEGRA_12x_SOC)\ + || defined(CONFIG_ARCH_TEGRA_3x_SOC) + case 2: + tristate_dap_3(tristate); + break; + case 3: + tristate_dap_4(tristate); + break; +#endif + default: + pr_warn("Invalid DAP port\n"); + break; + } + return 0; +} +EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_tristate_dap); + +int tegra_alt_asoc_utils_set_rate(struct tegra_asoc_audio_clock_info *data, + int srate, + int mclk, + int clk_out_rate) +{ + int new_baseclock; + bool clk_change; + int err; + + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + new_baseclock = 56448000; + else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30) + new_baseclock = 564480000; + else + new_baseclock = 282240000; + break; + case 8000: + case 16000: + case 32000: + case 48000: + case 64000: + case 96000: + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + new_baseclock = 73728000; + else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30) + new_baseclock = 552960000; + else + new_baseclock = 368640000; + break; + default: + return -EINVAL; + } + + clk_change = ((new_baseclock != data->set_baseclock) || + (mclk != data->set_mclk)); + if (!clk_change) + return 0; + + /* Don't change rate if already one dai-link is using it */ + if (data->lock_count) + return -EINVAL; + + data->set_baseclock = 0; + data->set_mclk = 0; + + if (data->clk_pll_a_state) { + clk_disable_unprepare(data->clk_pll_a); + data->clk_pll_a_state = 0; + } + + if (data->clk_pll_a_out0_state) { + clk_disable_unprepare(data->clk_pll_a_out0); + data->clk_pll_a_out0_state = 0; + } + + if (data->clk_cdev1_state) { + clk_disable_unprepare(data->clk_cdev1); + data->clk_cdev1_state = 0; + } + + err = clk_set_rate(data->clk_pll_a, new_baseclock); + if (err) { + dev_err(data->dev, "Can't set pll_a rate: %d\n", err); + return err; + } + + err = clk_set_rate(data->clk_pll_a_out0, mclk); + if (err) { + dev_err(data->dev, "Can't set clk_pll_a_out0 rate: %d\n", err); + return err; + } + + err = clk_set_rate(data->clk_cdev1, clk_out_rate); + if (err) { + dev_err(data->dev, "Can't set clk_cdev1 rate: %d\n", err); + return err; + } + + err = clk_prepare_enable(data->clk_pll_a); + if (err) { + dev_err(data->dev, "Can't enable pll_a: %d\n", err); + return err; + } + data->clk_pll_a_state = 1; + + err = clk_prepare_enable(data->clk_pll_a_out0); + if (err) { + dev_err(data->dev, "Can't enable pll_a_out0: %d\n", err); + return err; + } + data->clk_pll_a_out0_state = 1; + + err = clk_prepare_enable(data->clk_cdev1); + if (err) { + dev_err(data->dev, "Can't enable cdev1: %d\n", err); + return err; + } + data->clk_cdev1_state = 1; + + data->set_baseclock = new_baseclock; + data->set_mclk = mclk; + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_set_rate); + +void tegra_alt_asoc_utils_lock_clk_rate(struct tegra_asoc_audio_clock_info *data, + int lock) +{ + if (lock) + data->lock_count++; + else if (data->lock_count) + data->lock_count--; +} +EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_lock_clk_rate); + +int tegra_alt_asoc_utils_clk_enable(struct tegra_asoc_audio_clock_info *data) +{ + int err; + + err = clk_prepare_enable(data->clk_cdev1); + if (err) { + dev_err(data->dev, "Can't enable cdev1: %d\n", err); + return err; + } + data->clk_cdev1_state = 1; + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_clk_enable); + +int tegra_alt_asoc_utils_clk_disable(struct tegra_asoc_audio_clock_info *data) +{ + clk_disable_unprepare(data->clk_cdev1); + data->clk_cdev1_state = 0; + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_clk_disable); + +int tegra_alt_asoc_utils_init(struct tegra_asoc_audio_clock_info *data, + struct device *dev, struct snd_soc_card *card) +{ + int ret; + + data->dev = dev; + data->card = card; + + data->clk_pll_p_out1 = clk_get_sys(NULL, "pll_p_out1"); + if (IS_ERR(data->clk_pll_p_out1)) { + dev_err(data->dev, "Can't retrieve clk pll_p_out1\n"); + ret = PTR_ERR(data->clk_pll_p_out1); + goto err; + } + + if (of_machine_is_compatible("nvidia,tegra20")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA20; + else if (of_machine_is_compatible("nvidia,tegra30")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA30; + else if (of_machine_is_compatible("nvidia,tegra114")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA114; + else if (of_machine_is_compatible("nvidia,tegra148")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA148; + else if (of_machine_is_compatible("nvidia,tegra124")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA124; + else if (!dev->of_node) { + /* non-DT is always Tegra20 */ +#if defined(CONFIG_ARCH_TEGRA_2x_SOC) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA20; +#elif defined(CONFIG_ARCH_TEGRA_3x_SOC) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA30; +#elif defined(CONFIG_ARCH_TEGRA_11x_SOC) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA114; +#elif defined(CONFIG_ARCH_TEGRA_14x_SOC) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA148; +#elif defined(CONFIG_ARCH_TEGRA_12x_SOC) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA124; +#endif + } else + /* DT boot, but unknown SoC */ + return -EINVAL; + + data->clk_pll_a = clk_get_sys(NULL, "pll_a"); + if (IS_ERR(data->clk_pll_a)) { + dev_err(data->dev, "Can't retrieve clk pll_a\n"); + ret = PTR_ERR(data->clk_pll_a); + goto err_put_pll_p_out1; + } + + data->clk_pll_a_out0 = clk_get_sys(NULL, "pll_a_out0"); + if (IS_ERR(data->clk_pll_a_out0)) { + dev_err(data->dev, "Can't retrieve clk pll_a_out0\n"); + ret = PTR_ERR(data->clk_pll_a_out0); + goto err_put_pll_a; + } + + data->clk_m = clk_get_sys(NULL, "clk_m"); + if (IS_ERR(data->clk_m)) { + dev_err(data->dev, "Can't retrieve clk clk_m\n"); + ret = PTR_ERR(data->clk_m); + goto err; + } + + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + data->clk_cdev1 = clk_get_sys(NULL, "cdev1"); + else + data->clk_cdev1 = clk_get_sys("extern1", NULL); + + if (IS_ERR(data->clk_cdev1)) { + dev_err(data->dev, "Can't retrieve clk cdev1\n"); + ret = PTR_ERR(data->clk_cdev1); + goto err_put_pll_a_out0; + } + + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + data->clk_out1 = ERR_PTR(-ENOENT); + else { + data->clk_out1 = clk_get_sys("clk_out_1", "extern1"); + if (IS_ERR(data->clk_out1)) { + dev_err(data->dev, "Can't retrieve clk out1\n"); + ret = PTR_ERR(data->clk_out1); + goto err_put_cdev1; + } + } + + ret = clk_prepare_enable(data->clk_cdev1); + if (ret) { + dev_err(data->dev, "Can't enable clk cdev1/extern1"); + goto err_put_out1; + } + data->clk_cdev1_state = 1; + + if (!IS_ERR(data->clk_out1)) { + ret = clk_prepare_enable(data->clk_out1); + if (ret) { + dev_err(data->dev, "Can't enable clk out1"); + goto err_put_out1; + } + } + + ret = tegra_alt_asoc_utils_set_rate(data, 48000, 256 * 48000, 256 * 48000); + if (ret) + goto err_put_out1; + + return 0; + +err_put_out1: + if (!IS_ERR(data->clk_out1)) + clk_put(data->clk_out1); +err_put_cdev1: + clk_put(data->clk_cdev1); +err_put_pll_a_out0: + clk_put(data->clk_pll_a_out0); +err_put_pll_a: + clk_put(data->clk_pll_a); +err_put_pll_p_out1: + clk_put(data->clk_pll_p_out1); +err: + return ret; +} +EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_init); + +int tegra_alt_asoc_utils_set_parent(struct tegra_asoc_audio_clock_info *data, + int is_i2s_master) +{ + int ret = -ENODEV; + + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + return ret; + + if (is_i2s_master) { + ret = clk_set_parent(data->clk_cdev1, data->clk_pll_a_out0); + if (ret) { + dev_err(data->dev, "Can't set clk cdev1/extern1 parent"); + return ret; + } + } else { + ret = clk_set_parent(data->clk_cdev1, data->clk_m); + if (ret) { + dev_err(data->dev, "Can't set clk cdev1/extern1 parent"); + return ret; + } + + ret = clk_set_rate(data->clk_cdev1, 13000000); + if (ret) { + dev_err(data->dev, "Can't set clk rate"); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_set_parent); + +void tegra_alt_asoc_utils_fini(struct tegra_asoc_audio_clock_info *data) +{ + if (data->clk_cdev1_state) + clk_disable(data->clk_cdev1); + + if (!IS_ERR(data->clk_out1)) + clk_put(data->clk_out1); + + if (!IS_ERR(data->clk_pll_a_out0)) + clk_put(data->clk_pll_a_out0); + + if (!IS_ERR(data->clk_pll_a)) + clk_put(data->clk_pll_a); + + if (!IS_ERR(data->clk_pll_p_out1)) + clk_put(data->clk_pll_p_out1); +} +EXPORT_SYMBOL_GPL(tegra_alt_asoc_utils_fini); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra ASoC utility code"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra-alt/tegra_asoc_utils_alt.h b/sound/soc/tegra-alt/tegra_asoc_utils_alt.h new file mode 100644 index 000000000000..877f9b808873 --- /dev/null +++ b/sound/soc/tegra-alt/tegra_asoc_utils_alt.h @@ -0,0 +1,75 @@ +/* + * tegra_alt_asoc_utils.h - Definitions for MCLK and DAP Utility driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (c) 2011-2013 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA_ASOC_UTILS_ALT_H__ +#define __TEGRA_ASOC_UTILS_ALT_H_ + +struct clk; +struct device; + +enum tegra_asoc_utils_soc { + TEGRA_ASOC_UTILS_SOC_TEGRA20, + TEGRA_ASOC_UTILS_SOC_TEGRA30, + TEGRA_ASOC_UTILS_SOC_TEGRA114, + TEGRA_ASOC_UTILS_SOC_TEGRA148, + TEGRA_ASOC_UTILS_SOC_TEGRA124, +}; + +struct tegra_asoc_audio_clock_info { + struct device *dev; + struct snd_soc_card *card; + enum tegra_asoc_utils_soc soc; + struct clk *clk_pll_a; + int clk_pll_a_state; + struct clk *clk_pll_a_out0; + int clk_pll_a_out0_state; + struct clk *clk_cdev1; + int clk_cdev1_state; + struct clk *clk_out1; + struct clk *clk_m; + int clk_m_state; + struct clk *clk_pll_p_out1; + int set_mclk; + int lock_count; + int set_baseclock; +}; + +int tegra_alt_asoc_utils_set_rate(struct tegra_asoc_audio_clock_info *data, + int srate, + int mclk, + int clk_out_rate); +void tegra_alt_asoc_utils_lock_clk_rate( + struct tegra_asoc_audio_clock_info *data, + int lock); +int tegra_alt_asoc_utils_init(struct tegra_asoc_audio_clock_info *data, + struct device *dev, struct snd_soc_card *card); +void tegra_alt_asoc_utils_fini(struct tegra_asoc_audio_clock_info *data); + +int tegra_alt_asoc_utils_set_parent(struct tegra_asoc_audio_clock_info *data, + int is_i2s_master); +int tegra_alt_asoc_utils_clk_enable(struct tegra_asoc_audio_clock_info *data); +int tegra_alt_asoc_utils_clk_disable(struct tegra_asoc_audio_clock_info *data); +int tegra_alt_asoc_utils_register_ctls(struct tegra_asoc_audio_clock_info *data); + +int tegra_alt_asoc_utils_tristate_dap(int id, bool tristate); + +#endif diff --git a/sound/soc/tegra-alt/tegra_pcm_alt.c b/sound/soc/tegra-alt/tegra_pcm_alt.c new file mode 100644 index 000000000000..b66881ebcb64 --- /dev/null +++ b/sound/soc/tegra-alt/tegra_pcm_alt.c @@ -0,0 +1,282 @@ +/* + * tegra_alt_pcm.c - Tegra PCM driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (c) 2011-2013 NVIDIA CORPORATION. All rights reserved. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * Vijay Mali <vmali@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include "tegra_pcm_alt.h" + +static const struct snd_pcm_hardware tegra_alt_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 2, + .period_bytes_min = 128, + .period_bytes_max = PAGE_SIZE * 2, + .periods_min = 1, + .periods_max = 8, + .buffer_bytes_max = PAGE_SIZE * 8, + .fifo_size = 4, +}; + +static int tegra_alt_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + struct tegra_alt_pcm_runtime_data *prtd; + int ret; + + prtd = kzalloc(sizeof(struct tegra_alt_pcm_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + /* Set HW params now that initialization is complete */ + snd_soc_set_runtime_hwparams(substream, &tegra_alt_pcm_hardware); + + /* Ensure period size is multiple of 8 */ + ret = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 0x8); + if (ret) { + dev_err(dev, "failed to set constraint %d\n", ret); + kfree(prtd); + return ret; + } + + ret = snd_dmaengine_pcm_open_request_chan(substream, NULL, NULL); + if (ret) { + dev_err(dev, "dmaengine pcm open failed with err %d\n", ret); + kfree(prtd); + return ret; + } + + return 0; +} + +static int tegra_alt_pcm_close(struct snd_pcm_substream *substream) +{ + snd_dmaengine_pcm_close_release_chan(substream); + return 0; +} + +int tegra_alt_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + struct tegra_alt_pcm_dma_params *dmap; + struct dma_slave_config slave_config; + int ret; + + dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + ret = snd_hwparams_to_dma_slave_config(substream, params, + &slave_config); + if (ret) { + dev_err(dev, "hw params config failed with err %d\n", ret); + return ret; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + slave_config.dst_addr = dmap->addr; + slave_config.dst_maxburst = 4; + } else { + slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + slave_config.src_addr = dmap->addr; + slave_config.src_maxburst = 4; + } + slave_config.slave_id = dmap->req_sel; + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret < 0) { + dev_err(dev, "dma slave config failed with err %d\n", ret); + return ret; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +int tegra_alt_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int tegra_alt_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops tegra_alt_pcm_ops = { + .open = tegra_alt_pcm_open, + .close = tegra_alt_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = tegra_alt_pcm_hw_params, + .hw_free = tegra_alt_pcm_hw_free, + .trigger = snd_dmaengine_pcm_trigger, + .pointer = snd_dmaengine_pcm_pointer, + .mmap = tegra_alt_pcm_mmap, +}; + +static int tegra_alt_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream , size_t size) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->private_data = NULL; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->bytes = size; + + return 0; +} + +void tegra_alt_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + + substream = pcm->streams[stream].substream; + if (!substream) + return; + + buf = &substream->dma_buffer; + if (!buf->area) + return; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; +} + +static u64 tegra_dma_mask = DMA_BIT_MASK(32); + +int tegra_alt_pcm_dma_allocate(struct snd_soc_pcm_runtime *rtd, size_t size) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &tegra_dma_mask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = tegra_alt_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK, + size); + if (ret) + goto err; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = tegra_alt_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE, + size); + if (ret) + goto err_free_play; + } + + return 0; + +err_free_play: + tegra_alt_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); +err: + return ret; +} + +int tegra_alt_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + return tegra_alt_pcm_dma_allocate(rtd, + tegra_alt_pcm_hardware.buffer_bytes_max); +} + +void tegra_alt_pcm_free(struct snd_pcm *pcm) +{ + tegra_alt_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); + tegra_alt_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); +} + +static int tegra_alt_pcm_probe(struct snd_soc_platform *platform) +{ + platform->dapm.idle_bias_off = 1; + return 0; +} + +static struct snd_soc_platform_driver tegra_alt_pcm_platform = { + .ops = &tegra_alt_pcm_ops, + .pcm_new = tegra_alt_pcm_new, + .pcm_free = tegra_alt_pcm_free, + .probe = tegra_alt_pcm_probe, +}; + +int tegra_alt_pcm_platform_register(struct device *dev) +{ + return snd_soc_register_platform(dev, &tegra_alt_pcm_platform); +} +EXPORT_SYMBOL_GPL(tegra_alt_pcm_platform_register); + +void tegra_alt_pcm_platform_unregister(struct device *dev) +{ + snd_soc_unregister_platform(dev); +} +EXPORT_SYMBOL_GPL(tegra_alt_pcm_platform_unregister); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra Alt PCM ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra-alt/tegra_pcm_alt.h b/sound/soc/tegra-alt/tegra_pcm_alt.h new file mode 100644 index 000000000000..b6d4990e03be --- /dev/null +++ b/sound/soc/tegra-alt/tegra_pcm_alt.h @@ -0,0 +1,52 @@ +/* + * tegra_pcm_alt.h - Definitions for Tegra PCM driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (c) 2011-2013 NVIDIA CORPORATION. All rights reserved. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA_PCM_ALT_H__ +#define __TEGRA_PCM_ALT_H__ + +#define MAX_DMA_REQ_COUNT 2 + +struct tegra_alt_pcm_dma_params { + unsigned long addr; + unsigned long wrap; + unsigned long width; + unsigned long req_sel; +}; + +struct tegra_alt_pcm_runtime_data { + int running; + int disable_intr; + dma_addr_t avp_dma_addr; +}; + +int tegra_alt_pcm_platform_register(struct device *dev); +void tegra_alt_pcm_platform_unregister(struct device *dev); + +#endif |