summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorSumit Bhattacharya <sumitb@nvidia.com>2014-01-31 20:47:55 +0530
committerBharat Nihalani <bnihalani@nvidia.com>2014-03-12 01:24:43 -0700
commit180b20cc27a1499f56dff41ddddc043c217bc047 (patch)
tree5653af26ee73f5832cc8e78062f5f5ef6ccde90f /sound
parent0adcfe1763f1f56563fe260ad7190b3c915643d4 (diff)
ASoC: Tegra: Add offload rendering support
Add tegra offload support for pcm and compress data rendering. Using this interface CPU can offload pcm or compress audio rendering to AVP. Bug 1399922 Change-Id: I0e750f5f6f1a6eb1f624f07d91a3cb47b11365e1 Signed-off-by: Sumit Bhattacharya <sumitb@nvidia.com> Reviewed-on: http://git-master/r/362761 Reviewed-by: Automatic_Commit_Validation_User Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/tegra/Kconfig18
-rw-r--r--sound/soc/tegra/Makefile4
-rw-r--r--sound/soc/tegra/tegra30_avp.c1334
-rw-r--r--sound/soc/tegra/tegra_offload.c710
-rw-r--r--sound/soc/tegra/tegra_offload.h95
5 files changed, 2161 insertions, 0 deletions
diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig
index c4d521fdcc6e..47504058c332 100644
--- a/sound/soc/tegra/Kconfig
+++ b/sound/soc/tegra/Kconfig
@@ -6,6 +6,15 @@ config SND_SOC_TEGRA
help
Say Y or M here if you want support for SoC audio on Tegra.
+config SND_SOC_TEGRA_OFFLOAD
+ tristate "Audio offload support for Tegra SoC"
+ depends on SND_SOC_TEGRA
+ help
+ Say Y or M here if you want support for offload audio on Tegra.
+ When offload support is enabled offload platform is used and it
+ sends both PCM and compressed data to AVP/DSP for decode and
+ rendering.
+
config SND_SOC_TEGRA20_DAS
tristate
depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC
@@ -93,6 +102,15 @@ config SND_SOC_TEGRA30_I2S
Tegra30 I2S interface. You will also need to select the individual
machine drivers to support below.
+config SND_SOC_TEGRA30_AVP
+ tristate
+ depends on SND_SOC_TEGRA
+ select SND_SOC_TEGRA_OFFLOAD
+ help
+ Say Y or M if you want to add support for the Tegra30 AVP rendering
+ module. You will also need to select the individual machine drivers to
+ support below.
+
config SND_SOC_TEGRA_WM8753
tristate "SoC Audio support for Tegra boards using a WM8753 codec"
depends on SND_SOC_TEGRA && I2C
diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile
index aafa85571a74..5fa017bd8bf1 100644
--- a/sound/soc/tegra/Makefile
+++ b/sound/soc/tegra/Makefile
@@ -4,6 +4,7 @@ subdir-ccflags-y := -Werror
# Tegra platform Support
snd-soc-tegra-pcm-objs := tegra_pcm.o
+snd-soc-tegra-offload-objs := tegra_offload.o
snd-soc-tegra-utils-objs += tegra_asoc_utils.o
snd-soc-tegra20-das-objs := tegra20_das.o
snd-soc-tegra20-i2s-objs := tegra20_i2s.o
@@ -12,10 +13,12 @@ snd-soc-tegra30-ahub-objs := tegra30_ahub.o
snd-soc-tegra30-i2s-objs := tegra30_i2s.o
snd-soc-tegra30-spdif-objs := tegra30_spdif.o
snd-soc-tegra30-dam-objs := tegra30_dam.o
+snd-soc-tegra30-avp-objs := tegra30_avp.o
snd-soc-tegra-dmic-objs := tegra_dmic.o
obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o
obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-utils.o
+obj-$(CONFIG_SND_SOC_TEGRA_OFFLOAD) += snd-soc-tegra-offload.o
obj-$(CONFIG_SND_SOC_TEGRA20_DAS) += snd-soc-tegra20-das.o
obj-$(CONFIG_SND_SOC_TEGRA20_I2S) += snd-soc-tegra20-i2s.o
obj-$(CONFIG_SND_SOC_TEGRA20_SPDIF) += snd-soc-tegra20-spdif.o
@@ -23,6 +26,7 @@ obj-$(CONFIG_SND_SOC_TEGRA30_AHUB) += snd-soc-tegra30-ahub.o
obj-$(CONFIG_SND_SOC_TEGRA30_DAM) += snd-soc-tegra30-dam.o
obj-$(CONFIG_SND_SOC_TEGRA30_I2S) += snd-soc-tegra30-i2s.o
obj-$(CONFIG_SND_SOC_TEGRA30_SPDIF) += snd-soc-tegra30-spdif.o
+obj-$(CONFIG_SND_SOC_TEGRA30_AVP) += snd-soc-tegra30-avp.o
obj-$(CONFIG_SND_SOC_TEGRA_DMIC) += snd-soc-tegra-dmic.o
# Tegra machine Support
diff --git a/sound/soc/tegra/tegra30_avp.c b/sound/soc/tegra/tegra30_avp.c
new file mode 100644
index 000000000000..012ef284ee18
--- /dev/null
+++ b/sound/soc/tegra/tegra30_avp.c
@@ -0,0 +1,1334 @@
+/*
+ * tegra30_avp.c - Tegra AVP audio driver
+ *
+ * Author: Sumit Bhattacharya <sumitb@nvidia.com>
+ * Copyright (c) 2014, 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.
+ */
+
+/*
+#define VERBOSE_DEBUG 1
+#define DEBUG 1
+#define DEBUG_AVP
+*/
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/completion.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+#include <linux/kthread.h>
+
+#include <linux/dmaengine.h>
+
+#include <sound/compress_offload.h>
+#include <sound/compress_params.h>
+#include <sound/pcm.h>
+
+#include <linux/tegra_avp_audio.h>
+#include "tegra_offload.h"
+
+#include <linux/tegra_nvavp.h>
+#include "../../drivers/media/platform/tegra/nvavp/nvavp_os.h"
+
+#define DRV_NAME "tegra30-avp-audio"
+
+/******************************************************************************
+ * DEFINES, ENUMS & GLOBALS
+ *****************************************************************************/
+#define AVP_UCODE_HEADER "NVAVPAPP"
+#define AVP_UCODE_HEADER_SIZE (sizeof(AVP_UCODE_HEADER) - 1)
+#define AVP_INIT_SAMPLE_RATE 48000
+
+#define AVP_COMPR_THRESHOLD (4 * 1024)
+#define AVP_UNITY_STREAM_VOLUME 0x10000
+
+#define AVP_CMD_BUFFER_SIZE 256
+
+enum avp_compr_formats {
+ avp_compr_mp3,
+ avp_compr_aac,
+ avp_compr_max,
+};
+
+#ifdef DEBUG_AVP
+#define DUMP_AVP_STATUS(s) \
+ pr_info("%s %d : id %d:: SS %d Halt %d RP %d %d PP %d LP %d FD %d "\
+ "WP %d WC %d DS %d DBWP %d SR %d DBRP %d\n",\
+ __func__, __LINE__,\
+ s->id,\
+ (int)s->stream->stream_state_current,\
+ s->stream->halted,\
+ s->stream->source_buffer_read_position,\
+ s->stream->source_buffer_read_position_fraction,\
+ s->stream->source_buffer_presentation_position,\
+ s->stream->source_buffer_linear_position,\
+ s->stream->source_buffer_frames_decoded,\
+ s->stream->source_buffer_write_position,\
+ s->stream->source_buffer_write_count,\
+ (int)s->audio_avp->audio_engine->device_state_current,\
+ s->audio_avp->audio_engine->device_buffer_write_position,\
+ s->stream->stream_notification_request, \
+ s->audio_avp->audio_engine->device_buffer_read_position);
+#else
+#define DUMP_AVP_STATUS(s)
+#endif
+
+/******************************************************************************
+ * STRUCTURES
+ *****************************************************************************/
+
+static const struct tegra30_avp_ucode_desc {
+ int max_mem_size;
+ const char *bin_name;
+} avp_ucode_desc[] = {
+ [CODEC_PCM] = {30000, "nvavp_aud_ucode.bin" },
+ [CODEC_MP3] = {60000, "nvavp_mp3dec_ucode.bin" },
+ [CODEC_AAC] = {100000, "nvavp_aacdec_ucode.bin" },
+};
+
+struct tegra30_avp_audio_dma {
+ struct tegra_offload_dma_params params;
+ struct dma_chan *chan;
+ struct dma_async_tx_descriptor *chan_desc;
+ struct dma_slave_config chan_slave_config;
+ dma_cookie_t chan_cookie;
+
+ int use_count;
+ int active_count;
+};
+
+struct tegra30_avp_stream {
+ struct tegra30_avp_audio *audio_avp;
+ struct tegra_offload_mem source_buf;
+ struct stream_data *stream;
+ enum avp_audio_stream_id id;
+ int period_size;
+
+ /* TODO : Use spinlock in appropriate places */
+ spinlock_t lock;
+
+ unsigned int last_notification_offset;
+ unsigned int notification_received;
+ unsigned int source_buffer_offset;
+
+ void (*notify_cb)(void *args);
+ void *notify_args;
+};
+
+struct tegra30_avp_audio {
+ struct device *dev;
+
+ nvavp_clientctx_t nvavp_client;
+ struct audio_engine_data *audio_engine;
+ struct tegra30_avp_stream avp_stream[RENDERSW_MAX_STREAMS];
+
+ struct tegra_offload_mem ucode_mem;
+ struct tegra_offload_mem cmd_buf_mem;
+ struct tegra_offload_mem param_mem;
+
+ unsigned int *cmd_buf;
+ int cmd_buf_idx;
+ int stream_active_count;
+ struct tegra30_avp_audio_dma audio_dma;
+ spinlock_t lock;
+};
+
+struct snd_compr_caps tegra30_avp_compr_caps[SND_COMPRESS_CAPTURE + 1] = {
+ [SND_COMPRESS_PLAYBACK] = {
+ .num_codecs = avp_compr_max,
+ .direction = SND_COMPRESS_PLAYBACK,
+ .min_fragment_size = 1024,
+ .max_fragment_size = 1024 * 1024, /* 1 MB */
+ .min_fragments = 2,
+ .max_fragments = 1024,
+ .codecs = {
+ [0] = SND_AUDIOCODEC_MP3,
+ [1] = SND_AUDIOCODEC_AAC,
+ },
+ },
+ [SND_COMPRESS_CAPTURE] = {
+ .num_codecs = 0,
+ .direction = SND_COMPRESS_CAPTURE,
+ },
+};
+
+struct snd_compr_codec_caps tegra30_avp_compr_codec_caps[] = {
+ [avp_compr_mp3] = {
+ .codec = SND_AUDIOCODEC_MP3,
+ .num_descriptors = 1,
+ .descriptor = {
+ [0] = {
+ .max_ch = 2,
+ .sample_rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .bit_rate = {
+ [0] = 32000,
+ [1] = 64000,
+ [2] = 128000,
+ [3] = 256000,
+ [4] = 320000,
+ },
+ .num_bitrates = 5,
+ .rate_control =
+ SND_RATECONTROLMODE_CONSTANTBITRATE |
+ SND_RATECONTROLMODE_VARIABLEBITRATE,
+ .profiles = 0,
+ .modes = SND_AUDIOCHANMODE_MP3_STEREO,
+ .formats = SND_AUDIOSTREAMFORMAT_UNDEFINED,
+ .min_buffer = 1024,
+ },
+ },
+ },
+ [avp_compr_aac] = {
+ .codec = SND_AUDIOCODEC_AAC,
+ .num_descriptors = 1,
+ .descriptor = {
+ [0] = {
+ .max_ch = 2,
+ .sample_rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .bit_rate = {
+ [0] = 32000,
+ [1] = 64000,
+ [2] = 128000,
+ [3] = 256000,
+ [4] = 320000,
+ },
+ .num_bitrates = 5,
+ .rate_control =
+ SND_RATECONTROLMODE_CONSTANTBITRATE |
+ SND_RATECONTROLMODE_VARIABLEBITRATE,
+ .profiles = SND_AUDIOPROFILE_AAC,
+ .modes = SND_AUDIOMODE_AAC_LC,
+ .formats = SND_AUDIOSTREAMFORMAT_MP4ADTS,
+ .min_buffer = 1024,
+ },
+ },
+ },
+};
+
+static struct tegra30_avp_audio *avp_audio_ctx;
+
+/******************************************************************************
+ * PRIVATE FUNCTIONS
+ *****************************************************************************/
+
+static int tegra30_avp_mem_alloc(struct tegra_offload_mem *mem, size_t size)
+{
+ mem->virt_addr = dma_alloc_coherent(avp_audio_ctx->dev, size,
+ &mem->phys_addr, GFP_KERNEL);
+ if (!mem->virt_addr) {
+ dev_err(avp_audio_ctx->dev, "Failed to allocate memory");
+ return -ENOMEM;
+ }
+ mem->bytes = size;
+ mem->dev = avp_audio_ctx->dev;
+ memset(mem->virt_addr, 0, mem->bytes);
+
+ dev_vdbg(mem->dev, "%s::virt %p phys %llx size %d\n", __func__,
+ mem->virt_addr, (u64)mem->phys_addr, (int)size);
+ return 0;
+}
+
+static void tegra30_avp_mem_free(struct tegra_offload_mem *mem)
+{
+ if (mem->virt_addr)
+ dma_free_coherent(mem->dev, mem->bytes,
+ mem->virt_addr, mem->phys_addr);
+}
+
+static int tegra30_avp_load_ucode(void)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct audio_engine_data *audio_engine;
+ const struct firmware *ucode_fw;
+ const struct tegra30_avp_ucode_desc *ucode_desc;
+ int ucode_size = 0, ucode_offset = 0, total_ucode_size = 0;
+ int i, ret = 0;
+
+ dev_vdbg(audio_avp->dev, "%s", __func__);
+
+ for (i = 0; i < ARRAY_SIZE(avp_ucode_desc); i++)
+ total_ucode_size += avp_ucode_desc[i].max_mem_size;
+
+ /* allocate memory for Ucode */
+ ret = tegra30_avp_mem_alloc(&audio_avp->ucode_mem, total_ucode_size);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to allocate ucode memory");
+ return ret;
+ }
+
+ /* allocate memory for parameters */
+ ret = tegra30_avp_mem_alloc(&audio_avp->param_mem,
+ sizeof(struct audio_engine_data));
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to allocate param memory");
+ goto err_ucode_mem_free;
+ }
+ audio_engine =
+ (struct audio_engine_data *)audio_avp->param_mem.virt_addr;
+ audio_avp->audio_engine = audio_engine;
+ audio_engine->codec_ucode_base_address =
+ audio_avp->ucode_mem.phys_addr;
+
+ for (i = 0; i < ARRAY_SIZE(avp_ucode_desc); i++) {
+ ucode_desc = &avp_ucode_desc[i];
+
+ ret = request_firmware(&ucode_fw, ucode_desc->bin_name,
+ audio_avp->dev);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "cannot read audio firmware %s",
+ ucode_desc->bin_name);
+ goto err_param_mem_free;
+ }
+
+ ucode_size = ucode_fw->size;
+ if (ucode_size <= 0) {
+ dev_err(audio_avp->dev, "Invalid ucode size.");
+ ret = -EINVAL;
+ release_firmware(ucode_fw);
+ goto err_param_mem_free;
+ }
+ dev_vdbg(audio_avp->dev, "%s ucode size = %d bytes",
+ ucode_desc->bin_name, ucode_size);
+
+ /* Read ucode */
+ if (strncmp((const char *)ucode_fw->data, AVP_UCODE_HEADER,
+ AVP_UCODE_HEADER_SIZE)) {
+ dev_err(audio_avp->dev, "ucode Header mismatch");
+ ret = -EINVAL;
+ release_firmware(ucode_fw);
+ goto err_param_mem_free;
+ }
+
+ memcpy((char *)audio_avp->ucode_mem.virt_addr + ucode_offset,
+ ucode_fw->data + AVP_UCODE_HEADER_SIZE,
+ ucode_size - AVP_UCODE_HEADER_SIZE);
+
+ audio_engine->codec_ucode_desc[i][UCODE_DESC_OFFSET] =
+ ucode_offset;
+ audio_engine->codec_ucode_desc[i][UCODE_DESC_SIZE] =
+ ucode_desc->max_mem_size;
+ ucode_offset += ucode_desc->max_mem_size;
+
+ release_firmware(ucode_fw);
+ }
+
+ /* allocate memory for command buffer */
+ ret = tegra30_avp_mem_alloc(&audio_avp->cmd_buf_mem,
+ AVP_CMD_BUFFER_SIZE);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to allocate cmd buffer memory");
+ goto err_param_mem_free;
+ }
+ audio_avp->cmd_buf = (unsigned int *)audio_avp->cmd_buf_mem.virt_addr;
+
+ /* Set command */
+ audio_avp->cmd_buf_idx = 0;
+ audio_avp->cmd_buf[audio_avp->cmd_buf_idx++] =
+ NVE26E_CH_OPCODE_INCR(NVE276_SET_MICROCODE_A, 3);
+ audio_avp->cmd_buf[audio_avp->cmd_buf_idx++] = 0;
+ audio_avp->cmd_buf[audio_avp->cmd_buf_idx++] =
+ audio_avp->ucode_mem.phys_addr;
+ audio_avp->cmd_buf[audio_avp->cmd_buf_idx++] =
+ avp_ucode_desc[CODEC_PCM].max_mem_size - 8;
+ audio_avp->cmd_buf[audio_avp->cmd_buf_idx++] =
+ NVE26E_CH_OPCODE_INCR(NVE276_PARAMETER_METHOD(0), 1);
+ audio_avp->cmd_buf[audio_avp->cmd_buf_idx++] =
+ audio_avp->param_mem.phys_addr;
+
+ ret = nvavp_pushbuffer_submit_audio(audio_avp->nvavp_client,
+ audio_avp->cmd_buf_mem.phys_addr,
+ audio_avp->cmd_buf_idx);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "pushbuffer_submit failed %d", ret);
+ ret = -EINVAL;
+ goto err_cmd_buf_mem_free;
+ }
+ dev_vdbg(audio_avp->dev, "Successfully loaded audio ucode");
+ return 0;
+
+err_cmd_buf_mem_free:
+ tegra30_avp_mem_free(&audio_avp->cmd_buf_mem);
+err_param_mem_free:
+ tegra30_avp_mem_free(&audio_avp->param_mem);
+err_ucode_mem_free:
+ tegra30_avp_mem_free(&audio_avp->ucode_mem);
+ return ret;
+}
+
+static void tegra30_avp_audio_engine_init(void)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct audio_engine_data *audio_engine = audio_avp->audio_engine;
+ int i = 0;
+
+ /* Initialize audio_engine_data */
+#if defined(CONFIG_ARCH_TEGRA_14x_SOC) || defined(CONFIG_ARCH_TEGRA_12x_SOC)
+ audio_engine->chip_id = NV_TEGRA_T148;
+#else
+ audio_engine->chip_id = NV_TEGRA_T114;
+#endif
+ audio_engine->device_state_target = KSSTATE_STOP;
+ audio_engine->device_state_current = KSSTATE_STOP;
+
+#ifdef DEBUG_AVP
+ audio_engine->profile_state = PROFILE_RENDER_OVERALL_PROCESSING;
+#endif
+ audio_engine->device_format.rate = AVP_INIT_SAMPLE_RATE;
+ audio_engine->device_format.bits_per_sample = 16;
+ audio_engine->device_format.channels = 2;
+
+ /* Initialize stream memory */
+ for (i = 0; i < max_stream_id; i++) {
+ struct tegra30_avp_stream *avp_stream;
+ struct stream_data *stream;
+ int j = 0;
+
+ avp_stream = &audio_avp->avp_stream[i];
+ memset(avp_stream, 0, sizeof(struct tegra30_avp_stream));
+
+ avp_stream->id = i;
+
+ stream = &audio_engine->stream[avp_stream->id];
+ avp_stream->stream = stream;
+ spin_lock_init(&avp_stream->lock);
+
+ stream->stream_state_target = KSSTATE_STOP;
+ stream->source_buffer_write_position = 0;
+ stream->source_buffer_write_count = 0;
+ stream->stream_params.rate = AVP_INIT_SAMPLE_RATE;
+
+ for (j = 0; j < RENDERSW_MAX_CHANNELS; j++)
+ stream->stream_volume[j] = AVP_UNITY_STREAM_VOLUME;
+
+ stream->source_buffer_read_position = 0;
+ stream->source_buffer_presentation_position = 0;
+ stream->source_buffer_linear_position = 0;
+ stream->source_buffer_presentation_position = 0;
+ stream->source_buffer_frames_decoded = 0;
+ stream->stream_state_current = KSSTATE_STOP;
+
+ stream->stream_params.rate = AVP_INIT_SAMPLE_RATE;
+ stream->stream_params.bits_per_sample = 16;
+ stream->stream_params.channels = 2;
+
+ avp_stream->audio_avp = audio_avp;
+ }
+}
+
+static int tegra30_avp_audio_alloc_dma(struct tegra_offload_dma_params *params)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_audio_dma *dma = &audio_avp->audio_dma;
+ struct audio_engine_data *audio_engine = audio_avp->audio_engine;
+ dma_cap_mask_t mask;
+ int ret = 0;
+
+ dev_vdbg(audio_avp->dev, "%s : use %d",
+ __func__, dma->use_count);
+
+ dma->use_count++;
+ if (dma->use_count > 1)
+ return 0;
+
+ memcpy(&dma->params, params, sizeof(struct tegra_offload_dma_params));
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_CYCLIC, mask);
+ dma->chan = dma_request_channel(mask, NULL, NULL);
+ if (dma->chan == NULL) {
+ dev_err(audio_avp->dev, "Failed to allocate DMA chan.");
+ return -ENOMEM;
+ }
+
+ /* Only playback is supported */
+ dma->chan_slave_config.direction = DMA_MEM_TO_DEV;
+ dma->chan_slave_config.dst_addr_width = dma->params.width;
+ dma->chan_slave_config.dst_addr = dma->params.addr;
+ dma->chan_slave_config.dst_maxburst = dma->params.max_burst;
+ dma->chan_slave_config.slave_id = dma->params.req_sel;
+
+ ret = dmaengine_slave_config(dma->chan, &dma->chan_slave_config);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "dma slave config failed.err %d.", ret);
+ return ret;
+ }
+ audio_engine->apb_channel_handle = dma->chan->chan_id;
+ return 0;
+}
+
+static void tegra30_avp_audio_free_dma(void)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_audio_dma *dma = &audio_avp->audio_dma;
+
+ dev_vdbg(audio_avp->dev, "%s : use %d",
+ __func__, dma->use_count);
+
+ if (dma->use_count <= 0)
+ return;
+
+ dma->use_count--;
+ if (dma->use_count)
+ return;
+
+ dma_release_channel(dma->chan);
+}
+
+static int tegra30_avp_audio_start_dma(void)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_audio_dma *dma = &audio_avp->audio_dma;
+ struct audio_engine_data *audio_engine = audio_avp->audio_engine;
+
+ dev_vdbg(audio_avp->dev, "%s: active %d.", __func__, dma->active_count);
+
+ dma->active_count++;
+ if (dma->active_count > 1)
+ return 0;
+
+ dma->chan_desc = dmaengine_prep_dma_cyclic(dma->chan,
+ (dma_addr_t)audio_engine->device_buffer_avp,
+ DEVICE_BUFFER_SIZE,
+ DEVICE_BUFFER_SIZE,
+ dma->chan_slave_config.direction,
+ DMA_CTRL_ACK);
+ if (!dma->chan_desc) {
+ dev_err(audio_avp->dev, "Failed to prep cyclic dma");
+ return -ENODEV;
+ }
+ dma->chan_cookie = dmaengine_submit(dma->chan_desc);
+ dma_async_issue_pending(dma->chan);
+ return 0;
+}
+
+static int tegra30_avp_audio_stop_dma(void)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_audio_dma *dma = &audio_avp->audio_dma;
+
+ dev_vdbg(audio_avp->dev, "%s : active %d",
+ __func__, dma->active_count);
+
+ dma->active_count--;
+ if (dma->active_count > 0)
+ return 0;
+
+ dmaengine_terminate_all(dma->chan);
+ return 0;
+}
+
+static int tegra30_avp_audio_execute(void)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ uint32_t cmd_idx = audio_avp->cmd_buf_idx;
+ int ret = 0;
+
+ dev_vdbg(audio_avp->dev, "%s", __func__);
+
+ /* Set command */
+ audio_avp->cmd_buf[cmd_idx++] =
+ NVE26E_CH_OPCODE_INCR(NVE276_EXECUTE, 1);
+ audio_avp->cmd_buf[cmd_idx++] = 0; /* NVE276_EXECUTE_APPID */
+
+ ret = nvavp_pushbuffer_submit_audio(audio_avp->nvavp_client,
+ audio_avp->cmd_buf_mem.phys_addr,
+ cmd_idx);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "nvavp_pushbuffer_submit_audio failed");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int tegra30_avp_audio_set_state(enum KSSTATE new_state)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct audio_engine_data *audio_engine = audio_avp->audio_engine;
+ enum KSSTATE old_state;
+
+ old_state = audio_engine->device_state_target;
+
+ dev_vdbg(audio_avp->dev, "%s : state %d -> %d",
+ __func__, old_state, new_state);
+
+ if (old_state == new_state)
+ return 0;
+
+ audio_engine->device_state_target = new_state;
+ /* TODO : Need a way to wait till AVP device state changes */
+ if (new_state == KSSTATE_RUN)
+ tegra30_avp_audio_execute();
+
+ return 0;
+}
+
+
+/* Call this function with stream lock held */
+static int tegra30_avp_stream_set_state(int id, enum KSSTATE new_state)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+ struct stream_data *stream = avp_stream->stream;
+ enum KSSTATE old_state = stream->stream_state_target;
+ int ret = 0;
+
+ dev_vdbg(audio_avp->dev, "%s : id %d state %d -> %d", __func__, id,
+ old_state, new_state);
+
+ if (old_state == new_state)
+ return 0;
+
+ if (old_state == KSSTATE_STOP) {
+ switch (stream->stream_format) {
+ case FORMAT_MP3:
+ nvavp_enable_audio_clocks(audio_avp->nvavp_client,
+ NVAVP_MODULE_ID_VCP);
+ break;
+ case FORMAT_AAC:
+ nvavp_enable_audio_clocks(audio_avp->nvavp_client,
+ NVAVP_MODULE_ID_VCP);
+ break;
+ default:
+ break;
+ }
+ }
+
+ stream->stream_state_target = new_state;
+ /* TODO : Need a way to wait till AVP stream state changes */
+
+ if (new_state == KSSTATE_STOP) {
+ switch (stream->stream_format) {
+ case FORMAT_MP3:
+ nvavp_disable_audio_clocks(audio_avp->nvavp_client,
+ NVAVP_MODULE_ID_VCP);
+ break;
+ case FORMAT_AAC:
+ nvavp_disable_audio_clocks(audio_avp->nvavp_client,
+ NVAVP_MODULE_ID_VCP);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (new_state == KSSTATE_STOP) {
+ stream->source_buffer_write_position = 0;
+ stream->source_buffer_write_count = 0;
+ avp_stream->last_notification_offset = 0;
+ avp_stream->notification_received = 0;
+ avp_stream->source_buffer_offset = 0;
+ }
+
+ if (new_state == KSSTATE_RUN)
+ tegra30_avp_audio_start_dma();
+ else
+ tegra30_avp_audio_stop_dma();
+
+ return ret;
+}
+
+/* TODO : review ISR to make it more optimized if possible */
+static void tegra30_avp_stream_notify(void)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream;
+ struct stream_data *stream;
+ int i;
+
+ /* dev_vdbg(audio_avp->dev, "tegra30_avp_stream_notify"); */
+
+ for (i = 0; i < max_stream_id; i++) {
+ avp_stream = &audio_avp->avp_stream[i];
+ stream = avp_stream->stream;
+
+ if (avp_stream->notify_cb &&
+ (stream->stream_state_target != KSSTATE_RUN))
+ continue;
+ while (stream->stream_notification_request >
+ avp_stream->notification_received) {
+ int data_processed =
+ stream->source_buffer_read_position -
+ avp_stream->last_notification_offset;
+
+ if (data_processed < 0)
+ data_processed += stream->source_buffer_size;
+ avp_stream->notification_received++;
+
+ while (data_processed >= avp_stream->period_size) {
+ avp_stream->notify_cb(avp_stream->notify_args);
+ avp_stream->last_notification_offset +=
+ avp_stream->period_size;
+ avp_stream->last_notification_offset %=
+ stream->source_buffer_size;
+ data_processed -= avp_stream->period_size;
+ }
+ }
+ }
+}
+
+/******************************************************************************
+ * EXPORTED FUNCTIONS
+ *****************************************************************************/
+/* Device APIs */
+static int tegra30_avp_set_hw_rate(int rate)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct audio_engine_data *audio_engine = audio_avp->audio_engine;
+
+ dev_vdbg(audio_avp->dev, "%s rate %d", __func__, rate);
+
+ if (!audio_engine) {
+ dev_err(audio_avp->dev, "AVP platform not initialized.");
+ return -ENODEV;
+ }
+
+ audio_engine->device_format.rate = rate;
+ return 0;
+}
+
+static int tegra30_avp_alloc_shared_mem(struct tegra_offload_mem *mem,
+ int bytes)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ int ret = 0;
+
+ ret = tegra30_avp_mem_alloc(mem, bytes);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "%s: Failed. ret %d", __func__, ret);
+ return ret;
+ }
+ return 0;
+}
+
+static void tegra30_avp_free_shared_mem(struct tegra_offload_mem *mem)
+{
+ tegra30_avp_mem_free(mem);
+}
+
+/* PCM APIs */
+static int tegra30_avp_pcm_open(int *id)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct audio_engine_data *audio_engine = audio_avp->audio_engine;
+ int ret = 0;
+
+ dev_vdbg(audio_avp->dev, "%s", __func__);
+
+ if (!audio_avp->nvavp_client) {
+ ret = tegra_nvavp_audio_client_open(&audio_avp->nvavp_client);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to open nvavp.");
+ return ret;
+ }
+ }
+ if (!audio_engine) {
+ ret = tegra30_avp_load_ucode();
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to load ucode.");
+ return ret;
+ }
+ tegra30_avp_audio_engine_init();
+ nvavp_register_audio_cb(audio_avp->nvavp_client,
+ tegra30_avp_stream_notify);
+ audio_engine = audio_avp->audio_engine;
+ }
+
+ if (!audio_engine->stream[pcm_stream_id].stream_allocated)
+ *id = pcm_stream_id;
+ else if (!audio_engine->stream[pcm2_stream_id].stream_allocated)
+ *id = pcm2_stream_id;
+ else {
+ dev_err(audio_avp->dev, "All AVP PCM streams are busy");
+ return -EBUSY;
+ }
+ tegra30_avp_audio_set_state(KSSTATE_RUN);
+ return 0;
+}
+
+static int tegra30_avp_pcm_set_params(int id,
+ struct tegra_offload_pcm_params *params)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+ struct stream_data *stream = avp_stream->stream;
+ int ret = 0;
+
+ dev_vdbg(audio_avp->dev,
+ "%s id %d rate %d chan %d bps %d period size %d buf size %d",
+ __func__, id, params->rate, params->channels,
+ params->bits_per_sample, params->period_size,
+ params->buffer_size);
+
+ /* TODO : check validity of parameters */
+ if (!stream) {
+ dev_err(audio_avp->dev, "AVP platform not initialized.");
+ return -ENODEV;
+ }
+
+ stream->stream_notification_interval = params->period_size;
+ stream->stream_notification_enable = 1;
+ stream->stream_params.rate = params->rate;
+ stream->stream_params.channels = params->channels;
+ stream->stream_params.bits_per_sample = params->bits_per_sample;
+ avp_stream->period_size = params->period_size;
+
+ avp_stream->notify_cb = params->period_elapsed_cb;
+ avp_stream->notify_args = params->period_elapsed_args;
+
+ stream->source_buffer_system = params->source_buf.virt_addr;
+ stream->source_buffer_avp = params->source_buf.phys_addr;
+ stream->source_buffer_size = params->buffer_size;
+
+ /* Set DMA params */
+ ret = tegra30_avp_audio_alloc_dma(&params->dma_params);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to allocate DMA. ret %d", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int tegra30_avp_pcm_set_state(int id, int state)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+ struct stream_data *stream = avp_stream->stream;
+
+ dev_vdbg(audio_avp->dev, "%s : id %d state %d", __func__, id, state);
+
+ if (!stream) {
+ dev_err(audio_avp->dev, "AVP platform not initialized.");
+ return -ENODEV;
+ }
+
+ switch (state) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ tegra30_avp_stream_set_state(id, KSSTATE_RUN);
+ return 0;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ tegra30_avp_stream_set_state(id, KSSTATE_STOP);
+ return 0;
+ default:
+ dev_err(audio_avp->dev, "Unsupported state.");
+ return -EINVAL;
+ }
+}
+
+static void tegra30_avp_pcm_data_ready(int id, int bytes)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+ struct stream_data *stream = avp_stream->stream;
+
+ dev_vdbg(audio_avp->dev, "%s :id %d size %d", __func__, id, bytes);
+
+ stream->source_buffer_write_position += bytes;
+ stream->source_buffer_write_position %= stream->source_buffer_size;
+
+ avp_stream->source_buffer_offset += bytes;
+ while (avp_stream->source_buffer_offset >=
+ stream->stream_notification_interval) {
+ stream->source_buffer_write_count++;
+ avp_stream->source_buffer_offset -=
+ stream->stream_notification_interval;
+ }
+ return;
+}
+
+static size_t tegra30_avp_pcm_get_position(int id)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+ struct stream_data *stream = avp_stream->stream;
+ size_t pos = 0;
+
+ pos = (size_t)stream->source_buffer_read_position;
+
+ dev_vdbg(audio_avp->dev, "%s id %d pos %d", __func__, id, (u32)pos);
+
+ return pos;
+}
+
+/* Compress APIs */
+static int tegra30_avp_compr_open(int *id)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct audio_engine_data *audio_engine = audio_avp->audio_engine;
+ int ret = 0;
+
+ dev_vdbg(audio_avp->dev, "%s", __func__);
+
+ if (!audio_avp->nvavp_client) {
+ ret = tegra_nvavp_audio_client_open(&audio_avp->nvavp_client);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to open nvavp.");
+ return ret;
+ }
+ }
+ if (!audio_engine) {
+ ret = tegra30_avp_load_ucode();
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to load ucode.");
+ return ret;
+ }
+ tegra30_avp_audio_engine_init();
+ nvavp_register_audio_cb(audio_avp->nvavp_client,
+ tegra30_avp_stream_notify);
+ audio_engine = audio_avp->audio_engine;
+ }
+
+ if (!audio_engine->stream[decode_stream_id].stream_allocated)
+ *id = decode_stream_id;
+ else if (!audio_engine->stream[decode2_stream_id].stream_allocated)
+ *id = decode2_stream_id;
+ else {
+ dev_err(audio_avp->dev, "All AVP COMPR streams are busy");
+ return -EBUSY;
+ }
+ tegra30_avp_audio_set_state(KSSTATE_RUN);
+ return 0;
+}
+
+static int tegra30_avp_compr_set_params(int id,
+ struct tegra_offload_compr_params *params)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+ struct stream_data *stream = avp_stream->stream;
+ int ret = 0;
+
+ dev_vdbg(audio_avp->dev,
+ "%s: id %d codec %d rate %d ch %d bps %d buf %d x %d",
+ __func__, id, params->codec_type, params->rate,
+ params->channels, params->bits_per_sample,
+ params->fragment_size, params->fragments);
+
+ /* TODO : check validity of parameters */
+ if (!stream) {
+ dev_err(audio_avp->dev, "AVP platform not initialized.");
+ return -ENODEV;
+ }
+
+ if (params->codec_type == SND_AUDIOCODEC_MP3) {
+ stream->stream_format = FORMAT_MP3;
+ } else if (params->codec_type == SND_AUDIOCODEC_AAC) {
+ stream->stream_format = FORMAT_AAC;
+
+ /* AAC-LC is only supported profile*/
+ stream->u.aac.audio_profile = AAC_PROFILE_LC;
+ stream->u.aac.sampling_freq = params->rate;
+ stream->u.aac.payload_type = AAC_PAYLOAD_ADTS;
+ switch (params->rate) {
+ case 8000:
+ stream->u.aac.sampling_freq_index = 0xb;
+ break;
+ case 11025:
+ stream->u.aac.sampling_freq_index = 0xa;
+ break;
+ case 12000:
+ stream->u.aac.sampling_freq_index = 0x9;
+ break;
+ case 16000:
+ stream->u.aac.sampling_freq_index = 0x8;
+ break;
+ case 22050:
+ stream->u.aac.sampling_freq_index = 0x7;
+ break;
+ case 24000:
+ stream->u.aac.sampling_freq_index = 0x6;
+ break;
+ case 32000:
+ stream->u.aac.sampling_freq_index = 0x5;
+ break;
+ case 44100:
+ stream->u.aac.sampling_freq_index = 0x4;
+ break;
+ case 48000:
+ stream->u.aac.sampling_freq_index = 0x3;
+ break;
+ case 64000:
+ stream->u.aac.sampling_freq_index = 0x2;
+ break;
+ case 88200:
+ stream->u.aac.sampling_freq_index = 0x1;
+ break;
+ case 96000:
+ stream->u.aac.sampling_freq_index = 0x0;
+ break;
+ default:
+ dev_err(audio_avp->dev, "Unsupported rate");
+ return -EINVAL;
+ }
+ /* Only Stereo data is supported */
+ stream->u.aac.channel_configuration = params->channels;
+ } else {
+ dev_err(audio_avp->dev, "Invalid stream/codec type.");
+ return -EINVAL;
+ }
+
+ stream->stream_params.rate = params->rate;
+ stream->stream_params.channels = params->channels;
+ stream->stream_params.bits_per_sample = params->bits_per_sample;
+ avp_stream->period_size = params->fragment_size;
+
+ avp_stream->notify_cb = params->fragments_elapsed_cb;
+ avp_stream->notify_args = params->fragments_elapsed_args;
+
+ stream->source_buffer_size = (params->fragments *
+ params->fragment_size);
+ tegra30_avp_mem_alloc(&avp_stream->source_buf,
+ stream->source_buffer_size);
+
+ stream->source_buffer_system = avp_stream->source_buf.virt_addr;
+ stream->source_buffer_avp = avp_stream->source_buf.phys_addr;
+
+ if (stream->source_buffer_size > AVP_COMPR_THRESHOLD) {
+ stream->stream_notification_interval =
+ stream->source_buffer_size - AVP_COMPR_THRESHOLD;
+ } else {
+ stream->stream_notification_interval = avp_stream->period_size;
+ }
+ stream->stream_notification_enable = 1;
+
+ /* Set DMA params */
+ ret = tegra30_avp_audio_alloc_dma(&params->dma_params);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to allocate DMA. ret %d", ret);
+ return ret;
+ }
+
+ if ((params->codec_type == SND_AUDIOCODEC_MP3) ||
+ (params->codec_type == SND_AUDIOCODEC_AAC)) {
+ dev_info(audio_avp->dev, "\n*** STARTING %s ULP PLAYBACK ***\n",
+ (params->codec_type == SND_AUDIOCODEC_MP3) ? "MP3" : "AAC");
+ }
+ return 0;
+}
+
+static int tegra30_avp_compr_set_state(int id, int state)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+
+ dev_vdbg(audio_avp->dev, "%s : id %d state %d",
+ __func__, id, state);
+
+ switch (state) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ tegra30_avp_stream_set_state(id, KSSTATE_RUN);
+ return 0;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ tegra30_avp_stream_set_state(id, KSSTATE_STOP);
+ return 0;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ tegra30_avp_stream_set_state(id, KSSTATE_PAUSE);
+ return 0;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ tegra30_avp_stream_set_state(id, KSSTATE_RUN);
+ return 0;
+ case SND_COMPR_TRIGGER_DRAIN:
+ /* TODO */
+ return 0;
+ default:
+ dev_err(audio_avp->dev, "Unsupported state.");
+ return -EINVAL;
+ }
+}
+
+static void tegra30_avp_compr_data_ready(int id, int bytes)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+ struct stream_data *stream = avp_stream->stream;
+
+ dev_vdbg(audio_avp->dev, "%s : id %d size %d", __func__, id, bytes);
+
+ stream->source_buffer_write_position += bytes;
+ stream->source_buffer_write_position %= stream->source_buffer_size;
+
+ avp_stream->source_buffer_offset += bytes;
+ while (avp_stream->source_buffer_offset >=
+ stream->stream_notification_interval) {
+ stream->source_buffer_write_count++;
+ avp_stream->source_buffer_offset -=
+ stream->stream_notification_interval;
+ }
+ return;
+}
+
+static int tegra30_avp_compr_write(int id, char __user *buf, int bytes)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+ struct stream_data *stream = avp_stream->stream;
+ void *dst = stream->source_buffer_system +
+ stream->source_buffer_write_position;
+ int avail = 0;
+ int write = 0;
+ int ret = 0;
+
+ avail = stream->source_buffer_read_position -
+ stream->source_buffer_write_position;
+ if ((avail < 0) || (!avail &&
+ (stream->source_buffer_write_count ==
+ stream->stream_notification_request)))
+ avail += stream->source_buffer_size;
+
+ dev_vdbg(audio_avp->dev, "%s : id %d size %d", __func__, id, bytes);
+
+ /* TODO: check enough free space is available before writing */
+ bytes = (bytes > avail) ? avail : bytes;
+ if (!bytes) {
+ dev_dbg(audio_avp->dev, "No free space in ring buffer.");
+ DUMP_AVP_STATUS(avp_stream);
+ return bytes;
+ }
+
+ write = stream->source_buffer_size -
+ stream->source_buffer_write_position;
+ if (write > bytes) {
+ ret = copy_from_user(dst, buf, bytes);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to copy user data.");
+ return -EFAULT;
+ }
+ } else {
+ ret = copy_from_user(dst, buf, write);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to copy user data.");
+ return -EFAULT;
+ }
+
+ ret = copy_from_user(stream->source_buffer_system, buf + write,
+ bytes - write);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to copy user data.");
+ return -EFAULT;
+ }
+ }
+
+ stream->source_buffer_write_position += bytes;
+ stream->source_buffer_write_position %= stream->source_buffer_size;
+
+ avp_stream->source_buffer_offset += bytes;
+ while (avp_stream->source_buffer_offset >=
+ stream->stream_notification_interval) {
+ stream->source_buffer_write_count++;
+ avp_stream->source_buffer_offset -=
+ stream->stream_notification_interval;
+ }
+ DUMP_AVP_STATUS(avp_stream);
+ return bytes;
+}
+
+static int tegra30_avp_compr_get_position(int id,
+ struct snd_compr_tstamp *tstamp)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+ struct stream_data *stream = avp_stream->stream;
+
+ tstamp->byte_offset = stream->source_buffer_write_position;
+ tstamp->copied_total = stream->source_buffer_write_position +
+ (stream->source_buffer_write_count *
+ stream->stream_notification_interval);
+ tstamp->pcm_frames = stream->source_buffer_presentation_position;
+ tstamp->pcm_io_frames = stream->source_buffer_presentation_position;
+ tstamp->sampling_rate = stream->stream_params.rate;
+
+ dev_vdbg(audio_avp->dev, "%s id %d off %d copied %d pcm %d pcm io %d",
+ __func__, id, (int)tstamp->byte_offset,
+ (int)tstamp->copied_total, (int)tstamp->pcm_frames,
+ (int)tstamp->pcm_io_frames);
+
+ return 0;
+}
+
+static int tegra30_avp_compr_get_caps(struct snd_compr_caps *caps)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+
+ dev_vdbg(audio_avp->dev, "%s : dir %d", __func__, caps->direction);
+
+ if (caps->direction == SND_COMPRESS_PLAYBACK)
+ memcpy(caps, &tegra30_avp_compr_caps[SND_COMPRESS_PLAYBACK],
+ sizeof(struct snd_compr_caps));
+ else
+ memcpy(caps, &tegra30_avp_compr_caps[SND_COMPRESS_CAPTURE],
+ sizeof(struct snd_compr_caps));
+ return 0;
+}
+
+static int tegra30_avp_compr_get_codec_caps(struct snd_compr_codec_caps *codec)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+
+ dev_vdbg(audio_avp->dev, "%s : codec %d", __func__, codec->codec);
+
+ switch (codec->codec) {
+ case SND_AUDIOCODEC_MP3:
+ memcpy(codec, &tegra30_avp_compr_codec_caps[avp_compr_mp3],
+ sizeof(struct snd_compr_codec_caps));
+ return 0;
+ case SND_AUDIOCODEC_AAC:
+ memcpy(codec, &tegra30_avp_compr_codec_caps[avp_compr_aac],
+ sizeof(struct snd_compr_codec_caps));
+ return 0;
+ default:
+ dev_err(audio_avp->dev, "Unsupported codec %d", codec->codec);
+ return -EINVAL;
+ }
+}
+
+static int tegra30_avp_compr_set_volume(int id, int left, int right)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+ struct stream_data *stream = avp_stream->stream;
+
+ dev_vdbg(audio_avp->dev, "%s id %d left vol %d right vol %d",
+ __func__, id, left, right);
+
+ if (!stream) {
+ dev_err(audio_avp->dev, "AVP platform not initialized.");
+ return -ENODEV;
+ }
+
+ stream->stream_volume[0] = left;
+ stream->stream_volume[1] = right;
+
+ return 0;
+}
+
+/* Common APIs for pcm and compress */
+static void tegra30_avp_stream_close(int id)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+ struct stream_data *stream = avp_stream->stream;
+
+ dev_vdbg(audio_avp->dev, "%s id %d", __func__, id);
+
+ if (!stream) {
+ dev_err(audio_avp->dev, "AVP platform not initialized.");
+ return;
+ }
+ tegra30_avp_mem_free(&avp_stream->source_buf);
+ tegra30_avp_audio_free_dma();
+ stream->stream_allocated = 0;
+ tegra30_avp_stream_set_state(id, KSSTATE_STOP);
+ tegra30_avp_audio_set_state(KSSTATE_STOP);
+}
+
+static struct tegra_offload_ops avp_audio_platform = {
+ .device_ops = {
+ .set_hw_rate = tegra30_avp_set_hw_rate,
+ .alloc_shared_mem = tegra30_avp_alloc_shared_mem,
+ .free_shared_mem = tegra30_avp_free_shared_mem,
+ },
+ .pcm_ops = {
+ .stream_open = tegra30_avp_pcm_open,
+ .stream_close = tegra30_avp_stream_close,
+ .set_stream_params = tegra30_avp_pcm_set_params,
+ .set_stream_state = tegra30_avp_pcm_set_state,
+ .get_stream_position = tegra30_avp_pcm_get_position,
+ .data_ready = tegra30_avp_pcm_data_ready,
+ },
+ .compr_ops = {
+ .stream_open = tegra30_avp_compr_open,
+ .stream_close = tegra30_avp_stream_close,
+ .set_stream_params = tegra30_avp_compr_set_params,
+ .set_stream_state = tegra30_avp_compr_set_state,
+ .get_stream_position = tegra30_avp_compr_get_position,
+ .data_ready = tegra30_avp_compr_data_ready,
+ .write = tegra30_avp_compr_write,
+ .get_caps = tegra30_avp_compr_get_caps,
+ .get_codec_caps = tegra30_avp_compr_get_codec_caps,
+ .set_stream_volume = tegra30_avp_compr_set_volume,
+ },
+};
+
+static u64 tegra_dma_mask = DMA_BIT_MASK(32);
+static int tegra30_avp_audio_probe(struct platform_device *pdev)
+{
+ struct tegra30_avp_audio *audio_avp;
+
+ pr_debug("tegra30_avp_audio_platform_probe platform probe started\n");
+
+ audio_avp = devm_kzalloc(&pdev->dev, sizeof(*audio_avp), GFP_KERNEL);
+ if (!audio_avp) {
+ dev_err(&pdev->dev, "Can't allocate tegra30_avp_audio\n");
+ return -ENOMEM;
+ }
+ dev_set_drvdata(&pdev->dev, audio_avp);
+ audio_avp->dev = &pdev->dev;
+
+ /* set the ops */
+ if (tegra_register_offload_ops(&avp_audio_platform)) {
+ dev_err(&pdev->dev, "Failed to register avp audio device.");
+ return -EPROBE_DEFER;
+ }
+
+ spin_lock_init(&audio_avp->lock);
+ pdev->dev.dma_mask = &tegra_dma_mask;
+ pdev->dev.coherent_dma_mask = tegra_dma_mask;
+ avp_audio_ctx = audio_avp;
+ pr_info("tegra30_avp_audio_platform_probe successful.");
+ return 0;
+}
+
+static int tegra30_avp_audio_remove(struct platform_device *pdev)
+{
+ struct tegra30_avp_audio *audio_avp = dev_get_drvdata(&pdev->dev);
+
+ dev_vdbg(&pdev->dev, "%s", __func__);
+
+ tegra_nvavp_audio_client_release(audio_avp->nvavp_client);
+ tegra30_avp_mem_free(&audio_avp->cmd_buf_mem);
+ tegra30_avp_mem_free(&audio_avp->param_mem);
+ tegra30_avp_mem_free(&audio_avp->ucode_mem);
+
+ return 0;
+}
+
+static const struct of_device_id tegra30_avp_audio_of_match[] = {
+ { .compatible = "nvidia,tegra30-avp-audio", },
+ {},
+};
+
+static struct platform_driver tegra30_avp_audio_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = tegra30_avp_audio_of_match,
+ },
+ .probe = tegra30_avp_audio_probe,
+ .remove = tegra30_avp_audio_remove,
+};
+module_platform_driver(tegra30_avp_audio_driver);
+
+MODULE_AUTHOR("Sumit Bhattacharya <sumitb@nvidia.com>");
+MODULE_DESCRIPTION("Tegra30 AVP Audio driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_DEVICE_TABLE(of, tegra30_avp_audio_of_match);
diff --git a/sound/soc/tegra/tegra_offload.c b/sound/soc/tegra/tegra_offload.c
new file mode 100644
index 000000000000..e18e30ac8f63
--- /dev/null
+++ b/sound/soc/tegra/tegra_offload.c
@@ -0,0 +1,710 @@
+/*
+ * tegra30_offload.c - Tegra platform driver for offload rendering
+ *
+ * Author: Sumit Bhattacharya <sumitb@nvidia.com>
+ * Copyright (c) 2014, 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.
+ */
+
+/*
+#define VERBOSE_DEBUG 1
+#define DEBUG 1
+*/
+
+#include <linux/dma-mapping.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/compress_driver.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "tegra_pcm.h"
+#include "tegra_offload.h"
+
+#define DRV_NAME "tegra-offload"
+
+enum {
+ PCM_OFFLOAD_DAI,
+ COMPR_OFFLOAD_DAI,
+ MAX_OFFLOAD_DAI
+};
+
+struct tegra_offload_pcm_data {
+ struct tegra_offload_pcm_ops *ops;
+ int appl_ptr;
+ int stream_id;
+};
+
+struct tegra_offload_compr_data {
+ struct tegra_offload_compr_ops *ops;
+ struct snd_codec codec;
+ int stream_id;
+};
+
+static struct tegra_offload_ops offload_ops;
+static int tegra_offload_init_done;
+static DEFINE_MUTEX(tegra_offload_lock);
+
+static const struct snd_pcm_hardware tegra_offload_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_S16_LE,
+ .channels_min = 2,
+ .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,
+};
+
+int tegra_register_offload_ops(struct tegra_offload_ops *ops)
+{
+ mutex_lock(&tegra_offload_lock);
+ if (!ops) {
+ pr_err("Invalid ops pointer.");
+ return -EINVAL;
+ }
+
+ if (tegra_offload_init_done) {
+ pr_err("Offload ops already registered.");
+ return -EBUSY;
+ }
+ memcpy(&offload_ops, ops, sizeof(offload_ops));
+ tegra_offload_init_done = 1;
+ mutex_unlock(&tegra_offload_lock);
+
+ pr_info("succefully registered offload ops");
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_register_offload_ops);
+
+/* Compress playback related APIs */
+static void tegra_offload_compr_fragment_elapsed(void *arg)
+{
+ struct snd_compr_stream *stream = arg;
+
+ if (stream)
+ snd_compr_fragment_elapsed(stream);
+}
+
+static int tegra_offload_compr_open(struct snd_compr_stream *stream)
+{
+ struct snd_soc_pcm_runtime *rtd = stream->device->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct tegra_offload_compr_data *data;
+ int ret = 0;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ if (!tegra_offload_init_done) {
+ dev_err(dev, "Offload interface is not registered");
+ return -ENODEV;
+ }
+
+ if (!stream->device->dev)
+ stream->device->dev = dev;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ dev_vdbg(dev, "Failed to allocate tegra_offload_compr_data.");
+ return -ENOMEM;
+ }
+ data->ops = &offload_ops.compr_ops;
+
+ ret = data->ops->stream_open(&data->stream_id);
+ if (ret < 0) {
+ dev_err(dev, "Failed to open offload stream. err %d", ret);
+ return ret;
+ }
+
+ stream->runtime->private_data = data;
+ return 0;
+}
+
+static int tegra_offload_compr_free(struct snd_compr_stream *stream)
+{
+ struct device *dev = stream->device->dev;
+ struct tegra_offload_compr_data *data = stream->runtime->private_data;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ data->ops->stream_close(data->stream_id);
+ devm_kfree(dev, data);
+ return 0;
+}
+
+static int tegra_offload_compr_set_params(struct snd_compr_stream *stream,
+ struct snd_compr_params *params)
+{
+ struct device *dev = stream->device->dev;
+ struct tegra_offload_compr_data *data = stream->runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = stream->device->private_data;
+ struct tegra_pcm_dma_params *dmap;
+ struct tegra_offload_compr_params offl_params;
+ int ret = 0;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ dmap = rtd->cpu_dai->playback_dma_data;
+ if (!dmap) {
+ struct snd_soc_dpcm *dpcm;
+
+ list_for_each_entry(dpcm,
+ &rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients,
+ list_be) {
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+ struct snd_pcm_substream *be_substream =
+ snd_soc_dpcm_get_substream(be,
+ SNDRV_PCM_STREAM_PLAYBACK);
+
+ dmap = snd_soc_dai_get_dma_data(be->cpu_dai,
+ be_substream);
+ if (!dmap) {
+ dev_err(dev, "Failed to get DMA params.");
+ return -ENODEV;
+ }
+ /* TODO : Multiple BE to single FE not yet supported */
+ break;
+ }
+ }
+ offl_params.codec_type = params->codec.id;
+ offl_params.bits_per_sample = 16;
+ offl_params.rate = snd_pcm_rate_bit_to_rate(params->codec.sample_rate);
+ offl_params.channels = params->codec.ch_in;
+ offl_params.fragment_size = params->buffer.fragment_size;
+ offl_params.fragments = params->buffer.fragments;
+ offl_params.dma_params.addr = dmap->addr;
+ offl_params.dma_params.width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ offl_params.dma_params.req_sel = dmap->req_sel;
+ offl_params.dma_params.max_burst = 4;
+ offl_params.fragments_elapsed_cb = tegra_offload_compr_fragment_elapsed;
+ offl_params.fragments_elapsed_args = (void *)stream;
+
+ ret = data->ops->set_stream_params(data->stream_id, &offl_params);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set offload params. ret %d", ret);
+ return ret;
+ }
+ memcpy(&data->codec, &params->codec, sizeof(struct snd_codec));
+ return 0;
+}
+
+static int tegra_offload_compr_get_params(struct snd_compr_stream *stream,
+ struct snd_codec *codec)
+{
+ struct device *dev = stream->device->dev;
+ struct tegra_offload_compr_data *data = stream->runtime->private_data;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ memcpy(codec, &data->codec, sizeof(struct snd_codec));
+ return 0;
+}
+
+static int tegra_offload_compr_trigger(struct snd_compr_stream *stream, int cmd)
+{
+ struct device *dev = stream->device->dev;
+ struct tegra_offload_compr_data *data = stream->runtime->private_data;
+
+ dev_vdbg(dev, "%s : cmd %d", __func__, cmd);
+
+ data->ops->set_stream_state(data->stream_id, cmd);
+ return 0;
+}
+
+static int tegra_offload_compr_pointer(struct snd_compr_stream *stream,
+ struct snd_compr_tstamp *tstamp)
+{
+ struct device *dev = stream->device->dev;
+ struct tegra_offload_compr_data *data = stream->runtime->private_data;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ return data->ops->get_stream_position(data->stream_id, tstamp);
+}
+
+static int tegra_offload_compr_copy(struct snd_compr_stream *stream,
+ char __user *buf, size_t count)
+{
+ struct device *dev = stream->device->dev;
+ struct tegra_offload_compr_data *data = stream->runtime->private_data;
+
+ dev_vdbg(dev, "%s : bytes %d", __func__, (int)count);
+
+ return data->ops->write(data->stream_id, buf, count);
+}
+
+static int tegra_offload_compr_get_caps(struct snd_compr_stream *stream,
+ struct snd_compr_caps *caps)
+{
+ struct device *dev = stream->device->dev;
+ struct tegra_offload_compr_data *data = stream->runtime->private_data;
+ int ret = 0;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ caps->direction = stream->direction;
+ ret = data->ops->get_caps(caps);
+ if (ret < 0) {
+ dev_err(dev, "Failed to get compr caps. ret %d", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int tegra_offload_compr_codec_caps(struct snd_compr_stream *stream,
+ struct snd_compr_codec_caps *codec_caps)
+{
+ struct device *dev = stream->device->dev;
+ struct tegra_offload_compr_data *data = stream->runtime->private_data;
+ int ret = 0;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ if (!codec_caps->codec)
+ codec_caps->codec = data->codec.id;
+
+ ret = data->ops->get_codec_caps(codec_caps);
+ if (ret < 0) {
+ dev_err(dev, "Failed to get codec caps. ret %d", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static struct snd_compr_ops tegra_compr_ops = {
+
+ .open = tegra_offload_compr_open,
+ .free = tegra_offload_compr_free,
+ .set_params = tegra_offload_compr_set_params,
+ .get_params = tegra_offload_compr_get_params,
+ .trigger = tegra_offload_compr_trigger,
+ .pointer = tegra_offload_compr_pointer,
+ .copy = tegra_offload_compr_copy,
+ .get_caps = tegra_offload_compr_get_caps,
+ .get_codec_caps = tegra_offload_compr_codec_caps,
+};
+
+/* PCM playback related APIs */
+static void tegra_offload_pcm_period_elapsed(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+
+ if (substream)
+ snd_pcm_period_elapsed(substream);
+}
+
+static int tegra_offload_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct tegra_offload_pcm_data *data;
+ int ret = 0;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ if (!tegra_offload_init_done) {
+ dev_err(dev, "Offload interface is not registered");
+ return -ENODEV;
+ }
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ dev_vdbg(dev, "Failed to allocate tegra_offload_pcm_data.");
+ return -ENOMEM;
+ }
+
+ /* Set HW params now that initialization is complete */
+ snd_soc_set_runtime_hwparams(substream, &tegra_offload_pcm_hardware);
+
+ /* Ensure period size is multiple of 4 */
+ ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 0x4);
+ if (ret) {
+ dev_err(dev, "failed to set constraint %d\n", ret);
+ return ret;
+ }
+ data->ops = &offload_ops.pcm_ops;
+
+ ret = data->ops->stream_open(&data->stream_id);
+ if (ret < 0) {
+ dev_err(dev, "Failed to open offload stream. err %d", ret);
+ return ret;
+ }
+ offload_ops.device_ops.set_hw_rate(48000);
+ substream->runtime->private_data = data;
+ return 0;
+}
+
+static int tegra_offload_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct tegra_offload_pcm_data *data = substream->runtime->private_data;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ data->ops->stream_close(data->stream_id);
+ devm_kfree(dev, data);
+ return 0;
+}
+
+static int tegra_offload_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 tegra_offload_pcm_data *data = substream->runtime->private_data;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ struct tegra_pcm_dma_params *dmap;
+ struct tegra_offload_pcm_params offl_params;
+ int ret = 0;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ if (!dmap) {
+ struct snd_soc_dpcm *dpcm;
+
+ list_for_each_entry(dpcm,
+ &rtd->dpcm[substream->stream].be_clients, list_be) {
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+ struct snd_pcm_substream *be_substream =
+ snd_soc_dpcm_get_substream(be,
+ substream->stream);
+
+ dmap = snd_soc_dai_get_dma_data(be->cpu_dai,
+ be_substream);
+ if (!dmap) {
+ dev_err(dev, "Failed to get DMA params.");
+ return -ENODEV;
+ }
+ /* TODO : Multiple BE to single FE not yet supported */
+ break;
+ }
+ }
+
+ offl_params.bits_per_sample =
+ snd_pcm_format_width(params_format(params));
+ offl_params.rate = params_rate(params);
+ offl_params.channels = params_channels(params);
+ offl_params.buffer_size = params_buffer_bytes(params);
+ offl_params.period_size = params_period_size(params) *
+ ((offl_params.bits_per_sample >> 3) * offl_params.channels);
+
+ offl_params.dma_params.addr = dmap->addr;
+ offl_params.dma_params.width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ offl_params.dma_params.req_sel = dmap->req_sel;
+ offl_params.dma_params.max_burst = 4;
+
+ offl_params.source_buf.virt_addr = buf->area;
+ offl_params.source_buf.phys_addr = buf->addr;
+ offl_params.source_buf.bytes = buf->bytes;
+
+ offl_params.period_elapsed_cb = tegra_offload_pcm_period_elapsed;
+ offl_params.period_elapsed_args = (void *)substream;
+
+ ret = data->ops->set_stream_params(data->stream_id, &offl_params);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set avp params. ret %d", ret);
+ return ret;
+ }
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ return 0;
+}
+
+static int tegra_offload_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+static int tegra_offload_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct tegra_offload_pcm_data *data = substream->runtime->private_data;
+
+ dev_vdbg(dev, "%s : cmd %d", __func__, cmd);
+
+ data->ops->set_stream_state(data->stream_id, cmd);
+ if ((cmd == SNDRV_PCM_TRIGGER_STOP) ||
+ (cmd == SNDRV_PCM_TRIGGER_SUSPEND) ||
+ (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH))
+ data->appl_ptr = 0;
+
+ return 0;
+}
+
+static snd_pcm_uframes_t tegra_offload_pcm_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct tegra_offload_pcm_data *data = substream->runtime->private_data;
+ size_t position;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ position = data->ops->get_stream_position(data->stream_id);
+ return bytes_to_frames(substream->runtime, position);
+}
+
+static int tegra_offload_pcm_ack(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_offload_pcm_data *data = runtime->private_data;
+ int data_size = runtime->control->appl_ptr - data->appl_ptr;
+
+ if (data_size < 0)
+ data_size += runtime->boundary;
+
+ data->ops->data_ready(data->stream_id,
+ frames_to_bytes(runtime, data_size));
+ data->appl_ptr = runtime->control->appl_ptr;
+ return 0;
+}
+
+
+static const struct snd_soc_dapm_widget tegra_offload_widgets[] = {
+ /* FrontEnd DAIs */
+ SND_SOC_DAPM_AIF_IN("offload-pcm-playback", "pcm-playback", 0,
+ 0/*wreg*/, 0/*wshift*/, 0/*winvert*/),
+ SND_SOC_DAPM_AIF_IN("offload-compr-playback", "compr-playback", 0,
+ 0/*wreg*/, 0/*wshift*/, 0/*winvert*/),
+
+ /* BackEnd DAIs */
+ SND_SOC_DAPM_AIF_OUT("I2S0_OUT", "tegra30-i2s.0 Playback", 0,
+ 0/*wreg*/, 0/*wshift*/, 0/*winvert*/),
+ SND_SOC_DAPM_AIF_OUT("I2S1_OUT", "tegra30-i2s.1 Playback", 0,
+ 0/*wreg*/, 0/*wshift*/, 0/*winvert*/),
+ SND_SOC_DAPM_AIF_OUT("I2S2_OUT", "tegra30-i2s.2 Playback", 0,
+ 0/*wreg*/, 0/*wshift*/, 0/*winvert*/),
+ SND_SOC_DAPM_AIF_OUT("I2S3_OUT", "tegra30-i2s.3 Playback", 0,
+ 0/*wreg*/, 0/*wshift*/, 0/*winvert*/),
+ SND_SOC_DAPM_AIF_OUT("I2S4_OUT", "tegra30-i2s.4 Playback", 0,
+ 0/*wreg*/, 0/*wshift*/, 0/*winvert*/),
+
+};
+
+static struct snd_pcm_ops tegra_pcm_ops = {
+ .open = tegra_offload_pcm_open,
+ .close = tegra_offload_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = tegra_offload_pcm_hw_params,
+ .hw_free = tegra_offload_pcm_hw_free,
+ .trigger = tegra_offload_pcm_trigger,
+ .pointer = tegra_offload_pcm_pointer,
+ .ack = tegra_offload_pcm_ack,
+};
+
+static void tegra_offload_dma_free(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ struct tegra_offload_mem mem;
+
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ return;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ return;
+
+ mem.dev = buf->dev.dev;
+ mem.bytes = buf->bytes;
+ mem.virt_addr = buf->area;
+ mem.phys_addr = buf->addr;
+
+ offload_ops.device_ops.free_shared_mem(&mem);
+ buf->area = NULL;
+}
+
+static u64 tegra_dma_mask = DMA_BIT_MASK(32);
+static int tegra_offload_dma_allocate(struct snd_soc_pcm_runtime *rtd,
+ int stream, size_t size)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm *pcm = rtd->pcm;
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ struct tegra_offload_mem mem;
+ 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);
+
+ ret = offload_ops.device_ops.alloc_shared_mem(&mem, size);
+ if (ret < 0) {
+ dev_err(pcm->card->dev, "Failed to allocate memory");
+ return -ENOMEM;
+ }
+ buf->area = mem.virt_addr;
+ buf->addr = mem.phys_addr;
+ buf->private_data = NULL;
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = mem.dev;
+ buf->bytes = mem.bytes;
+ return 0;
+}
+
+static int tegra_offload_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct device *dev = rtd->platform->dev;
+
+ dev_vdbg(dev, "%s", __func__);
+
+ return tegra_offload_dma_allocate(rtd , SNDRV_PCM_STREAM_PLAYBACK,
+ tegra_offload_pcm_hardware.buffer_bytes_max);
+}
+
+static void tegra_offload_pcm_free(struct snd_pcm *pcm)
+{
+ tegra_offload_dma_free(pcm, SNDRV_PCM_STREAM_PLAYBACK);
+ pr_debug("%s", __func__);
+}
+
+static int tegra_offload_pcm_probe(struct snd_soc_platform *platform)
+{
+ pr_debug("%s", __func__);
+
+ snd_soc_dapm_new_controls(&platform->dapm, tegra_offload_widgets,
+ ARRAY_SIZE(tegra_offload_widgets));
+
+ snd_soc_dapm_new_widgets(&platform->dapm);
+ platform->dapm.idle_bias_off = 1;
+ return 0;
+}
+
+unsigned int tegra_offload_pcm_read(struct snd_soc_platform *platform,
+ unsigned int reg)
+{
+ return 0;
+}
+
+int tegra_offload_pcm_write(struct snd_soc_platform *platform, unsigned int reg,
+ unsigned int val)
+{
+ return 0;
+}
+
+static struct snd_soc_platform_driver tegra_offload_platform = {
+ .ops = &tegra_pcm_ops,
+ .compr_ops = &tegra_compr_ops,
+ .pcm_new = tegra_offload_pcm_new,
+ .pcm_free = tegra_offload_pcm_free,
+ .probe = tegra_offload_pcm_probe,
+ .read = tegra_offload_pcm_read,
+ .write = tegra_offload_pcm_write,
+};
+
+static struct snd_soc_dai_driver tegra_offload_dai[] = {
+ [PCM_OFFLOAD_DAI] = {
+ .name = "tegra-offload-pcm",
+ .id = 0,
+ .playback = {
+ .stream_name = "pcm-playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+ [COMPR_OFFLOAD_DAI] = {
+ .name = "tegra-offload-compr",
+ .id = 0,
+ .compress_dai = 1,
+ .playback = {
+ .stream_name = "compr-playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ }
+};
+
+static const struct snd_soc_component_driver tegra_offload_component = {
+ .name = "tegra-offload",
+};
+
+static int tegra_offload_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ pr_debug("tegra30_avp_pcm platform probe started\n");
+
+ ret = snd_soc_register_platform(&pdev->dev, &tegra_offload_platform);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register platform: %d\n", ret);
+ goto err;
+ }
+
+ ret = snd_soc_register_component(&pdev->dev, &tegra_offload_component,
+ tegra_offload_dai, ARRAY_SIZE(tegra_offload_dai));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register component: %d\n", ret);
+ goto err_unregister_platform;
+ }
+ pr_info("tegra_offload_platform probe successfull.");
+ return 0;
+
+err_unregister_platform:
+ snd_soc_unregister_platform(&pdev->dev);
+err:
+ return ret;
+}
+
+static int tegra_offload_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static const struct of_device_id tegra_offload_of_match[] = {
+ { .compatible = "nvidia,tegra-offload", },
+ {},
+};
+
+static struct platform_driver tegra_offload_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = tegra_offload_of_match,
+ },
+ .probe = tegra_offload_probe,
+ .remove = tegra_offload_remove,
+};
+module_platform_driver(tegra_offload_driver);
+
+MODULE_AUTHOR("Sumit Bhattacharya <sumitb@nvidia.com>");
+MODULE_DESCRIPTION("Tegra offload platform driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_DEVICE_TABLE(of, tegra_offload_of_match);
diff --git a/sound/soc/tegra/tegra_offload.h b/sound/soc/tegra/tegra_offload.h
new file mode 100644
index 000000000000..b0e1fb2e2a48
--- /dev/null
+++ b/sound/soc/tegra/tegra_offload.h
@@ -0,0 +1,95 @@
+/*
+ * tegra_offload.h - Definitions for Tegra offload platform driver interface
+ *
+ * Author: Sumit Bhattacharya <sumitb@nvidia.com>
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef __TEGRA_OFFLOAD_H__
+#define __TEGRA_OFFLOAD_H__
+
+struct tegra_offload_dma_params {
+ unsigned long addr;
+ unsigned long width;
+ unsigned long req_sel;
+ unsigned long max_burst;
+};
+
+struct tegra_offload_mem {
+ struct device *dev;
+ unsigned char *virt_addr;
+ dma_addr_t phys_addr;
+ size_t bytes;
+};
+
+struct tegra_offload_pcm_params {
+ int rate;
+ int channels;
+ int bits_per_sample;
+ int buffer_size; /* bytes */
+ int period_size; /* bytes */
+ struct tegra_offload_mem source_buf;
+ struct tegra_offload_dma_params dma_params;
+ void (*period_elapsed_cb)(void *args);
+ void *period_elapsed_args;
+};
+
+struct tegra_offload_compr_params {
+ unsigned int codec_type; /* SND_AUDIOCODEC_* type */
+ int rate;
+ int channels;
+ int bits_per_sample;
+ int fragment_size; /* bytes */
+ int fragments;
+ struct tegra_offload_dma_params dma_params;
+ void (*fragments_elapsed_cb)(void *args);
+ void *fragments_elapsed_args;
+};
+
+struct tegra_offload_pcm_ops {
+ int (*stream_open)(int *id);
+ void (*stream_close)(int id);
+ int (*set_stream_params)(int id,
+ struct tegra_offload_pcm_params *params);
+ int (*set_stream_state)(int id, int state);
+ size_t (*get_stream_position)(int id);
+ void (*data_ready)(int id, int bytes);
+};
+
+struct tegra_offload_compr_ops {
+ int (*stream_open)(int *id);
+ void (*stream_close)(int id);
+ int (*set_stream_params)(int id,
+ struct tegra_offload_compr_params *params);
+ int (*set_stream_state)(int id, int state);
+ int (*get_stream_position)(int id, struct snd_compr_tstamp *tstamp);
+ void (*data_ready)(int id, int bytes);
+ int (*write)(int id, char __user *buf, int bytes);
+ int (*get_caps)(struct snd_compr_caps *caps);
+ int (*get_codec_caps)(struct snd_compr_codec_caps *codec);
+ int (*set_stream_volume)(int id, int left, int right);
+};
+
+struct tegra_offload_device_ops {
+ int (*set_hw_rate)(int rate);
+ int (*alloc_shared_mem)(struct tegra_offload_mem *mem, int bytes);
+ void (*free_shared_mem)(struct tegra_offload_mem *mem);
+};
+
+struct tegra_offload_ops {
+ struct tegra_offload_device_ops device_ops;
+ struct tegra_offload_pcm_ops pcm_ops;
+ struct tegra_offload_compr_ops compr_ops;
+};
+
+int tegra_register_offload_ops(struct tegra_offload_ops *ops);
+#endif