--- /dev/null
- .sample_rates = SNDRV_PCM_RATE_8000_48000,
+/*
+ * tegra210_adsp_alt.c - Tegra ADSP 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.
+ *
+ * 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/module.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <../arch/arm/mach-tegra/iomap.h>
+#include <linux/completion.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
+#include <linux/tegra_pm_domains.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+#include <linux/kthread.h>
+#include <linux/tegra_nvadsp.h>
+#include <linux/irqchip/tegra-agic.h>
+
+#include <sound/pcm.h>
+#include <sound/core.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/compress_driver.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/tegra_nvfx.h>
+#include <sound/tegra_nvfx_apm.h>
+#include <sound/tegra_nvfx_plugin.h>
+
+#include "tegra210_adsp_alt.h"
+
+#define DRV_NAME "tegra210-adsp"
+
+/* Flag to enable/disable loading of ADSP firmware */
+#define ENABLE_ADSP 1
+
+static struct tegra210_adsp_app_desc {
+ const char name[NVADSP_NAME_SZ];
+ const char fw_name[NVADSP_NAME_SZ];
+ const uint32_t reg_start;
+ const uint32_t reg_end;
+ nvadsp_app_handle_t handle;
+} adsp_app_desc[] = {
+ {"apm", "nvapm.elf",
+ TEGRA210_ADSP_APM_IN1, TEGRA210_ADSP_APM_IN8},
+ {"mp3dec", "nvmp3dec.elf",
+ TEGRA210_ADSP_PLUGIN_MP3_DEC1, TEGRA210_ADSP_PLUGIN_MP3_DEC2},
+ {"adma", "nvadma.elf",
+ TEGRA210_ADSP_PLUGIN_ADMA1, TEGRA210_ADSP_PLUGIN_ADMA4},
+ {"spkprot", "nvspkprot.elf",
+ TEGRA210_ADSP_PLUGIN_SPKPROT, TEGRA210_ADSP_PLUGIN_SPKPROT},
+};
+
+/* ADSP APP specific structure */
+struct tegra210_adsp_app {
+ const struct tegra210_adsp_app_desc *desc;
+ nvadsp_app_info_t *info;
+ plugin_shared_mem_t *plugin;
+ apm_shared_state_t *apm; /* For a plugin it stores parent apm data */
+ struct nvadsp_mbox rx_mbox;
+ uint32_t reg;
+ uint32_t adma_chan; /* Valid for only ADMA app */
+ uint32_t fe:1; /* Whether the app is used as a FE APM */
+ uint32_t connect:1; /* if app is connected to a source */
+ void *private_data;
+ int (*msg_handler)(struct tegra210_adsp_app *, apm_msg_t *);
+};
+
+struct tegra210_adsp_pcm_rtd {
+ struct device *dev;
+ struct snd_pcm_substream *substream;
+ struct tegra210_adsp_app *fe_apm;
+};
+
+struct tegra210_adsp_compr_rtd {
+ struct device *dev;
+ struct snd_dma_buffer buf;
+ struct snd_compr_stream *cstream;
+ struct snd_codec codec;
+ struct tegra210_adsp_app *fe_apm;
+ int is_draining;
+};
+
+struct tegra210_adsp {
+ struct device *dev;
+ struct tegra210_adsp_app apps[TEGRA210_ADSP_VIRT_REG_MAX];
+ atomic_t reg_val[TEGRA210_ADSP_VIRT_REG_MAX];
+ DECLARE_BITMAP(adma_usage, TEGRA210_ADSP_ADMA_CHANNEL_COUNT);
+ struct mutex mutex;
+ int init_done;
+};
+
+static const struct snd_pcm_hardware adsp_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 = 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,
+};
+
+/* Following structure is ALSA-Compress specific */
+static struct snd_compr_caps
+ tegra210_adsp_compr_caps[SND_COMPRESS_CAPTURE + 1] = {
+ [SND_COMPRESS_PLAYBACK] = {
+ .num_codecs = 2,
+ .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,
+ },
+};
+
+/* Following structure is ALSA-Compress specific */
+static struct snd_compr_codec_caps adsp_compr_codec_caps[] = {
+ [SND_AUDIOCODEC_MP3] = {
+ .codec = SND_AUDIOCODEC_MP3,
+ .num_descriptors = 1,
+ .descriptor = {
+ [0] = {
+ .max_ch = 2,
- .sample_rates = SNDRV_PCM_RATE_8000_48000,
++ .sample_rates = {
++ [0] = SNDRV_PCM_RATE_8000_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,
+ },
+ },
+ },
+ [SND_AUDIOCODEC_AAC] = {
+ .codec = SND_AUDIOCODEC_AAC,
+ .num_descriptors = 1,
+ .descriptor = {
+ [0] = {
+ .max_ch = 2,
++ .sample_rates = {
++ [0] = SNDRV_PCM_RATE_8000_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 status_t tegra210_adsp_msg_handler(uint32_t msg, void *data);
+
+/*
+ * Utility functions
+ */
+/* ADSP virtual register read/write functions */
+static uint32_t tegra210_adsp_reg_read(struct tegra210_adsp *adsp, uint32_t reg)
+{
+ return atomic_read(&adsp->reg_val[reg]);
+}
+
+static void tegra210_adsp_reg_write(struct tegra210_adsp *adsp,
+ uint32_t reg, uint32_t val)
+{
+ atomic_set(&adsp->reg_val[reg], val);
+ dev_vdbg(adsp->dev, "%s : 0x%x -> 0x%x\n", __func__, reg, val);
+}
+
+static void tegra210_adsp_reg_update_bits(struct tegra210_adsp *adsp,
+ uint32_t reg, uint32_t mask,
+ uint32_t val)
+{
+ uint32_t temp;
+
+ temp = tegra210_adsp_reg_read(adsp, reg);
+ val = (val & mask) | (temp & ~mask);
+ tegra210_adsp_reg_write(adsp, reg, val);
+
+ dev_vdbg(adsp->dev, "%s : 0x%x -> 0x%x\n", __func__, reg, val);
+}
+
+/* API to get source widget id connected to a widget */
+static uint32_t tegra210_adsp_get_source(struct tegra210_adsp *adsp,
+ uint32_t reg)
+{
+ uint32_t source;
+
+ source = tegra210_adsp_reg_read(adsp, reg);
+ source &= TEGRA210_ADSP_WIDGET_SOURCE_MASK;
+ source >>= TEGRA210_ADSP_WIDGET_SOURCE_SHIFT;
+
+ return source;
+}
+/* ADSP shared memory allocate/free functions */
+static int tegra210_adsp_preallocate_dma_buffer(struct device *dev, size_t size,
+ struct snd_dma_buffer *buf)
+{
+ dev_vdbg(dev, "%s : size %d.", __func__, (uint32_t)size);
+
+ buf->area = nvadsp_alloc_coherent(size, &buf->addr, GFP_KERNEL);
+ if (!buf->area) {
+ dev_err(dev, "Failed to pre-allocated DMA buffer.");
+ return -ENOMEM;
+ }
+
+ buf->bytes = size;
+ buf->private_data = NULL;
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = dev;
+
+ return 0;
+}
+
+static void tegra210_adsp_deallocate_dma_buffer(struct snd_dma_buffer *buf)
+{
+ dev_vdbg(buf->dev.dev, "%s : size %d.", __func__, (uint32_t)buf->bytes);
+
+ if (!buf->area)
+ return;
+
+ nvadsp_free_coherent(buf->bytes, buf->area, buf->addr);
+ buf->area = NULL;
+ buf->addr = 0;
+}
+
+/* ADSP OS boot and init API */
+static int tegra210_adsp_init(struct tegra210_adsp *adsp)
+{
+ int i, ret = 0;
+
+ mutex_lock(&adsp->mutex);
+ ret = nvadsp_os_load();
+ if (ret < 0) {
+ dev_err(adsp->dev, "Failed to load OS.");
+ goto exit;
+ }
+
+ if (nvadsp_os_start()) {
+ dev_err(adsp->dev, "Failed to start OS");
+ goto exit;
+ }
+
+ /* Load ADSP audio apps */
+ for (i = 0; i < ARRAY_SIZE(adsp_app_desc); i++) {
+ adsp_app_desc[i].handle = nvadsp_app_load(
+ adsp_app_desc[i].name,
+ adsp_app_desc[i].fw_name);
+ if (ret < 0) {
+ dev_err(adsp->dev, "Failed to load app %s",
+ adsp_app_desc[i].name);
+ goto exit;
+ }
+ }
+ adsp->init_done = 1;
+
+exit:
+ mutex_unlock(&adsp->mutex);
+ return ret;
+}
+
+static void tegra210_adsp_deinit(struct tegra210_adsp *adsp)
+{
+ mutex_lock(&adsp->mutex);
+ if (adsp->init_done) {
+ /* TODO : Stop ADSP OS if possible */
+ adsp->init_done = 0;
+ }
+ mutex_unlock(&adsp->mutex);
+}
+
+/* ADSP-CPU message send-receive utility functions */
+static int tegra210_adsp_get_msg(apm_shared_state_t *apm, apm_msg_t *apm_msg)
+{
+ apm_msg->msgq_msg.size = MSGQ_MSG_WSIZE(apm_msg_t) -
+ MSGQ_MESSAGE_HEADER_WSIZE;
+ return msgq_dequeue_message(&apm->msgq_send.msgq,
+ &apm_msg->msgq_msg);
+}
+
+static int tegra210_adsp_send_msg(apm_shared_state_t *apm,
+ apm_msg_t *apm_msg, uint32_t flags)
+{
+ int ret = 0;
+
+ ret = msgq_queue_message(&apm->msgq_recv.msgq, &apm_msg->msgq_msg);
+ if (ret < 0)
+ return ret;
+
+ if (flags & TEGRA210_ADSP_MSG_FLAG_HOLD)
+ return 0;
+
+ return nvadsp_hwmbox_send_data(apm->mbox_id,
+ apm_cmd_msg_ready,
+ NVADSP_MBOX_SMSG);
+}
+
+static int tegra210_adsp_send_connect_msg(struct tegra210_adsp_app *src,
+ struct tegra210_adsp_app *dst,
+ uint32_t flags)
+{
+ apm_msg_t apm_msg;
+
+ apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(apm_fx_connect_params_t);
+ apm_msg.msg.call_params.size = sizeof(apm_fx_connect_params_t);
+ apm_msg.msg.call_params.method = nvfx_apm_method_fx_connect;
+ apm_msg.msg.fx_connect_params.plugin_src.pvoid = IS_APM_IN(src->reg) ?
+ NULL : src->plugin->plugin.pvoid;
+ apm_msg.msg.fx_connect_params.pin_src = 0;
+ apm_msg.msg.fx_connect_params.plugin_dst.pvoid = IS_APM_OUT(dst->reg) ?
+ NULL : dst->plugin->plugin.pvoid;
+ apm_msg.msg.fx_connect_params.pin_dst = 0;
+
+ return tegra210_adsp_send_msg(src->apm, &apm_msg, flags);
+}
+
+static int tegra210_adsp_send_io_buffer_msg(struct tegra210_adsp_app *app,
+ dma_addr_t addr, size_t size,
+ uint32_t flags)
+{
+ apm_msg_t apm_msg;
+
+ apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(apm_io_buffer_params_t);
+ apm_msg.msg.call_params.size = sizeof(apm_io_buffer_params_t);
+ apm_msg.msg.call_params.method = nvfx_apm_method_set_io_buffer;
+ apm_msg.msg.io_buffer_params.pin_type = IS_APM_IN(app->reg) ?
+ NVFX_PIN_TYPE_INPUT : NVFX_PIN_TYPE_OUTPUT;
+ apm_msg.msg.io_buffer_params.pin_id = 0;
+ apm_msg.msg.io_buffer_params.addr.ptr = (uint64_t)addr;
+ apm_msg.msg.io_buffer_params.size = size;
+
+ return tegra210_adsp_send_msg(app->apm, &apm_msg, flags);
+}
+
+static int tegra210_adsp_send_period_size_msg(struct tegra210_adsp_app *app,
+ size_t size, uint32_t flags)
+{
+ apm_msg_t apm_msg;
+
+ apm_msg.msgq_msg.size =
+ MSGQ_MSG_WSIZE(apm_notification_params_t);
+ apm_msg.msg.call_params.size =
+ sizeof(apm_notification_params_t);
+ apm_msg.msg.call_params.method = nvfx_apm_method_set_notification_size;
+ apm_msg.msg.notification_params.pin_type = IS_APM_IN(app->reg) ?
+ NVFX_PIN_TYPE_INPUT : NVFX_PIN_TYPE_OUTPUT;
+ apm_msg.msg.notification_params.pin_id = 0;
+ apm_msg.msg.notification_params.size = size;
+
+ return tegra210_adsp_send_msg(app->apm, &apm_msg, flags);
+}
+
+static int tegra210_adsp_adma_params_msg(struct tegra210_adsp_app *app,
+ nvfx_adma_init_params_t *params,
+ uint32_t flags)
+{
+ apm_msg_t apm_msg;
+
+ apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(apm_fx_set_param_params_t);
+ apm_msg.msg.call_params.size = sizeof(apm_fx_set_param_params_t);
+ apm_msg.msg.call_params.method = nvfx_apm_method_fx_set_param;
+ apm_msg.msg.fx_set_param_params.plugin.pvoid =
+ app->plugin->plugin.pvoid;
+
+ params->call_params.size = sizeof(nvfx_adma_init_params_t);
+ params->call_params.method = nvfx_adma_method_init;
+ memcpy(&apm_msg.msg.fx_set_param_params.params, params,
+ sizeof(*params));
+
+ return tegra210_adsp_send_msg(app->apm, &apm_msg, flags);
+}
+
+static int tegra210_adsp_send_state_msg(struct tegra210_adsp_app *app,
+ int32_t state, uint32_t flags)
+{
+ apm_msg_t apm_msg;
+
+ apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(nvfx_set_state_params_t);
+ apm_msg.msg.call_params.size = sizeof(nvfx_set_state_params_t);
+ apm_msg.msg.call_params.method = nvfx_method_set_state;
+ apm_msg.msg.state_params.state = state;
+
+ return tegra210_adsp_send_msg(app->apm, &apm_msg, flags);
+}
+
+static int tegra210_adsp_send_reset_msg(struct tegra210_adsp_app *app,
+ uint32_t flags)
+{
+ apm_msg_t apm_msg;
+
+ apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(nvfx_reset_params_t);
+ apm_msg.msg.call_params.size = sizeof(nvfx_reset_params_t);
+ apm_msg.msg.call_params.method = nvfx_method_reset;
+
+ return tegra210_adsp_send_msg(app->apm, &apm_msg, flags);
+}
+
+static int tegra210_adsp_send_eos_msg(struct tegra210_adsp_app *app,
+ uint32_t flags)
+{
+ apm_msg_t apm_msg;
+
+ apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(apm_eos_params_t);
+ apm_msg.msg.call_params.size = sizeof(apm_eos_params_t);
+ apm_msg.msg.call_params.method = nvfx_apm_method_set_eos;
+
+ return tegra210_adsp_send_msg(app->apm, &apm_msg, flags);
+}
+
+static int tegra210_adsp_send_pos_msg(struct tegra210_adsp_app *app,
+ uint32_t pos, uint32_t flags)
+{
+ apm_msg_t apm_msg;
+
+ apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(apm_position_params_t);
+ apm_msg.msg.call_params.size = sizeof(apm_position_params_t);
+ apm_msg.msg.call_params.method = nvfx_apm_method_set_position;
+ apm_msg.msg.position_params.pin_type = IS_APM_IN(app->reg) ?
+ NVFX_PIN_TYPE_INPUT : NVFX_PIN_TYPE_OUTPUT;
+ apm_msg.msg.position_params.pin_id = 0;
+ apm_msg.msg.position_params.offset = pos;
+
+ return tegra210_adsp_send_msg(app->apm, &apm_msg, flags);
+}
+
+/* ADSP app init/de-init APIs */
+static int tegra210_adsp_app_init(struct tegra210_adsp *adsp,
+ struct tegra210_adsp_app *app)
+{
+ int ret = 0;
+
+ /* If app is already open or it is APM output pin don't open app */
+ if (app->info || IS_APM_OUT(app->reg))
+ return 0;
+
+ app->info = nvadsp_app_init(app->desc->handle, NULL);
+ if (!app->info) {
+ dev_err(adsp->dev, "Failed to init app %s(%s).",
+ app->desc->name, app->desc->fw_name);
+ return -ENODEV;
+ }
+
+ app->plugin = PLUGIN_SHARED_MEM(app->info->mem.shared);
+ if (IS_APM_IN(app->reg)) {
+ uint32_t apm_out_reg = APM_OUT_START +
+ (app->reg - APM_IN_START);
+ struct tegra210_adsp_app *apm_out = &adsp->apps[apm_out_reg];
+
+ app->apm = APM_SHARED_STATE(app->info->mem.shared);
+
+ ret = nvadsp_mbox_open(&app->rx_mbox,
+ &app->apm->mbox_id,
+ app->desc->name, tegra210_adsp_msg_handler, app);
+ if (ret < 0) {
+ dev_err(adsp->dev, "Failed to init app %s(%s).",
+ app->desc->name, app->desc->fw_name);
+ goto err_app_exit;
+ }
+
+ ret = nvadsp_app_start(app->info);
+ if (ret < 0) {
+ dev_err(adsp->dev, "Failed to start adsp app");
+ goto err_mbox_close;
+ }
+ /* Copy APM IN app data to APM OUT app */
+ apm_out->info = app->info;
+ apm_out->plugin = app->plugin;
+ apm_out->apm = app->apm;
+ }
+ return 0;
+
+err_mbox_close:
+ nvadsp_mbox_close(&app->rx_mbox);
+err_app_exit:
+ app->info = NULL;
+ return 0;
+}
+
+static void tegra210_adsp_app_deinit(struct tegra210_adsp *adsp,
+ struct tegra210_adsp_app *app)
+{
+ if (!app || !app->desc)
+ return;
+
+ if (app->info && !IS_APM_OUT(app->reg)) {
+ nvadsp_mbox_close(&app->rx_mbox);
+ nvadsp_app_stop(app->info);
+ app->info = NULL;
+ app->plugin = NULL;
+ app->apm = NULL;
+
+ if (IS_APM_IN(app->reg)) {
+ uint32_t apm_out_reg = APM_OUT_START +
+ (app->reg - APM_IN_START);
+ struct tegra210_adsp_app *apm_out =
+ &adsp->apps[apm_out_reg];
+
+ apm_out->info = NULL;
+ apm_out->plugin = NULL;
+ apm_out->apm = NULL;
+ }
+ }
+}
+
+/* Recursive function to connect plugins under a APM */
+static int tegra210_adsp_connect_plugin(struct tegra210_adsp *adsp,
+ struct tegra210_adsp_app *app)
+{
+ struct tegra210_adsp_app *src;
+ uint32_t source;
+ int ret = 0;
+
+ source = tegra210_adsp_get_source(adsp, app->reg);
+ if (!IS_ADSP_APP(source))
+ return -ENODEV;
+
+ src = &adsp->apps[source];
+ if (!IS_APM_IN(src->reg)) {
+ ret = tegra210_adsp_connect_plugin(adsp, src);
+ if (ret < 0)
+ return ret;
+ }
+ app->apm = src->apm;
+
+ /* If App is already connected and source connections have not changed
+ no need to again send connect message */
+ if (!ret && app->connect)
+ return 0;
+
+ dev_vdbg(adsp->dev, "Connecting plugin 0x%x -> 0x%x",
+ src->reg, app->reg);
+
+ ret = tegra210_adsp_send_connect_msg(src, app,
+ TEGRA210_ADSP_MSG_FLAG_HOLD);
+ if (ret < 0) {
+ dev_err(adsp->dev, "Connect msg failed. err %d.", ret);
+ return ret;
+ }
+ app->connect = 1;
+
+ /* return 1 if new connection is established */
+ return 1;
+}
+
+/* API to connect two APMs */
+static int tegra210_adsp_connect_apm(struct tegra210_adsp *adsp,
+ struct tegra210_adsp_app *app)
+{
+ struct tegra210_adsp_app *src;
+ uint32_t source;
+ int ret = 0;
+
+ source = tegra210_adsp_get_source(adsp, app->reg);
+ if (!IS_ADSP_APP(source))
+ return -EPIPE;
+
+ src = &adsp->apps[source];
+
+ if (IS_APM_OUT(src->reg)) {
+ /* If both APMs are in connected state no need to
+ send connect message */
+ if (app->connect && src->connect)
+ return 0;
+
+ dev_vdbg(adsp->dev, "Connecting APM 0x%x -> 0x%x",
+ src->reg, app->reg);
+
+ ret = tegra210_adsp_send_connect_msg(src, app,
+ TEGRA210_ADSP_MSG_FLAG_HOLD);
+ if (ret < 0) {
+ dev_err(adsp->dev, "Connect msg failed. err %d.", ret);
+ return ret;
+ }
+ return 1;
+ }
+ return -EPIPE;
+}
+
+/* Iterate over all APMs and establish pending connections */
+static int tegra210_adsp_update_connection(struct tegra210_adsp *adsp)
+{
+ int i;
+
+ for (i = APM_OUT_START; i <= APM_OUT_END; i++)
+ tegra210_adsp_connect_plugin(adsp, &adsp->apps[i]);
+
+ for (i = APM_IN_START; i <= APM_IN_END; i++)
+ tegra210_adsp_connect_apm(adsp, &adsp->apps[i]);
+
+ return 0;
+}
+
+/* ADSP mailbox message handler */
+static status_t tegra210_adsp_msg_handler(uint32_t msg, void *data)
+{
+ struct tegra210_adsp_app *app = data;
+ apm_msg_t apm_msg;
+ int ret = 0;
+
+ switch (msg) {
+ case apm_cmd_msg_ready: {
+ ret = tegra210_adsp_get_msg(app->apm, &apm_msg);
+ if (ret < 0) {
+ pr_err("Dequeue failed %d.", ret);
+ break;
+ }
+
+ if (app->msg_handler)
+ return app->msg_handler(app, &apm_msg);
+ }
+ break;
+ default:
+ pr_err("Unsupported mailbox msg %d.", msg);
+ }
+ return 0;
+}
+
+static int tegra210_adsp_pcm_msg_handler(struct tegra210_adsp_app *app,
+ apm_msg_t *apm_msg)
+{
+ struct tegra210_adsp_pcm_rtd *prtd = app->private_data;
+
+ switch (apm_msg->msg.call_params.method) {
+ case nvfx_apm_method_set_position:
+ snd_pcm_period_elapsed(prtd->substream);
+ break;
+ default:
+ dev_err(prtd->dev, "Unsupported cmd %d.",
+ apm_msg->msg.call_params.method);
+ }
+ return 0;
+}
+
+static int tegra210_adsp_compr_msg_handler(struct tegra210_adsp_app *app,
+ apm_msg_t *apm_msg)
+{
+ struct tegra210_adsp_compr_rtd *prtd = app->private_data;
+
+ switch (apm_msg->msg.call_params.method) {
+ case nvfx_apm_method_set_position:
+ snd_compr_fragment_elapsed(prtd->cstream);
+ break;
+ case nvfx_apm_method_set_eos: {
+ if (!prtd->is_draining) {
+ dev_warn(prtd->dev, "EOS reached before drain");
+ break;
+ }
+ tegra210_adsp_send_state_msg(prtd->fe_apm,
+ nvfx_state_inactive, 0);
+ tegra210_adsp_send_reset_msg(prtd->fe_apm, 0);
+ snd_compr_drain_notify(prtd->cstream);
+ prtd->is_draining = 0;
+ }
+ break;
+ default:
+ dev_err(prtd->dev, "Unsupported cmd %d.",
+ apm_msg->msg.call_params.method);
+ }
+ return 0;
+}
+
+/* Compress call-back APIs */
+static int tegra210_adsp_compr_open(struct snd_compr_stream *cstream)
+{
+ struct snd_soc_pcm_runtime *rtd = cstream->device->private_data;
+ struct tegra210_adsp *adsp =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct tegra210_adsp_compr_rtd *prtd;
+ uint32_t fe_reg = rtd->codec_dai->id;
+ int i;
+
+ dev_vdbg(adsp->dev, "%s : DAI ID %d", __func__, rtd->codec_dai->id);
+
+ if (!adsp->init_done)
+ return -ENODEV;
+
+ prtd = devm_kzalloc(adsp->dev, sizeof(struct tegra210_adsp_compr_rtd),
+ GFP_KERNEL);
+ if (!prtd) {
+ dev_err(adsp->dev, "Failed to allocate adsp rtd.");
+ return -ENOMEM;
+ }
+
+ /* Find out the APM connected with ADSP-FE DAI */
+ for (i = APM_IN_START; i <= APM_IN_END; i++) {
+ struct tegra210_adsp_app *app = &adsp->apps[i];
+ uint32_t source = tegra210_adsp_get_source(adsp, app->reg);
+
+ if (source == fe_reg) {
+ app->msg_handler = tegra210_adsp_compr_msg_handler;
+ app->private_data = prtd;
+ app->fe = 1;
+ prtd->fe_apm = app;
+ break;
+ }
+ }
+
+ if (!prtd->fe_apm) {
+ dev_err(adsp->dev, "No FE APM found\n");
+ devm_kfree(adsp->dev, prtd);
+ return -ENODEV;
+ }
+
+ prtd->cstream = cstream;
+ prtd->dev = adsp->dev;
+ cstream->runtime->private_data = prtd;
+ return 0;
+}
+
+static int tegra210_adsp_compr_free(struct snd_compr_stream *cstream)
+{
+ struct tegra210_adsp_compr_rtd *prtd = cstream->runtime->private_data;
+
+ if (prtd) {
+ prtd->fe_apm->fe = 0;
+ devm_kfree(prtd->dev, prtd);
+ }
+
+ return 0;
+}
+
+static int tegra210_adsp_compr_set_params(struct snd_compr_stream *cstream,
+ struct snd_compr_params *params)
+{
+ struct tegra210_adsp_compr_rtd *prtd = cstream->runtime->private_data;
+ int ret = 0;
+
+ if (!prtd)
+ return -ENODEV;
+
+ dev_vdbg(prtd->dev, "%s codec %d rate %d chan %d frag size %d frag %d",
+ __func__, params->codec.id,
+ snd_pcm_rate_bit_to_rate(params->codec.sample_rate),
+ params->codec.ch_in, params->buffer.fragment_size,
+ params->buffer.fragments);
+
+ ret = tegra210_adsp_preallocate_dma_buffer(prtd->dev,
+ params->buffer.fragment_size * params->buffer.fragments,
+ &prtd->buf);
+ if (ret < 0)
+ return ret;
+
+ ret = tegra210_adsp_send_io_buffer_msg(prtd->fe_apm, prtd->buf.addr,
+ prtd->buf.bytes,
+ TEGRA210_ADSP_MSG_FLAG_HOLD);
+ if (ret < 0) {
+ dev_err(prtd->dev, "IO buffer send msg failed. err %d.", ret);
+ return ret;
+ }
+
+ ret = tegra210_adsp_send_period_size_msg(prtd->fe_apm,
+ params->buffer.fragment_size,
+ TEGRA210_ADSP_MSG_FLAG_HOLD);
+ if (ret < 0) {
+ dev_err(prtd->dev, "Period size send msg failed. err %d.", ret);
+ return ret;
+ }
+
+ memcpy(&prtd->codec, ¶ms->codec, sizeof(struct snd_codec));
+ return 0;
+}
+
+static int tegra210_adsp_compr_get_params(struct snd_compr_stream *cstream,
+ struct snd_codec *codec)
+{
+ struct tegra210_adsp_compr_rtd *prtd = cstream->runtime->private_data;
+
+ memcpy(codec, &prtd->codec, sizeof(struct snd_codec));
+ return 0;
+}
+
+static int tegra210_adsp_compr_trigger(struct snd_compr_stream *cstream,
+ int cmd)
+{
+ struct tegra210_adsp_compr_rtd *prtd = cstream->runtime->private_data;
+ int ret = 0;
+
+ dev_vdbg(prtd->dev, "%s : cmd %d", __func__, cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ ret = tegra210_adsp_send_state_msg(prtd->fe_apm,
+ nvfx_state_active, 0);
+ if (ret < 0) {
+ dev_err(prtd->dev, "Failed to set state start.");
+ return ret;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = tegra210_adsp_send_state_msg(prtd->fe_apm,
+ nvfx_state_active, 0);
+ if (ret < 0) {
+ dev_err(prtd->dev, "Failed to set state resume");
+ return ret;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = tegra210_adsp_send_state_msg(prtd->fe_apm,
+ nvfx_state_inactive, 0);
+ if (ret < 0) {
+ dev_err(prtd->dev, "Failed to set state stop");
+ return ret;
+ }
+
+ ret = tegra210_adsp_send_reset_msg(prtd->fe_apm, 0);
+ if (ret < 0) {
+ dev_err(prtd->dev, "Failed to reset");
+ return ret;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = tegra210_adsp_send_state_msg(prtd->fe_apm,
+ nvfx_state_inactive, 0);
+ if (ret < 0) {
+ dev_err(prtd->dev, "Failed to set state pause");
+ return ret;
+ }
+ break;
+ case SND_COMPR_TRIGGER_DRAIN:
+ prtd->is_draining = 1;
+ ret = tegra210_adsp_send_eos_msg(prtd->fe_apm, 0);
+ if (ret < 0) {
+ dev_err(prtd->dev, "Failed to set state drain");
+ return ret;
+ }
+ break;
+
+ default:
+ dev_err(prtd->dev, "Unsupported state.");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int tegra210_adsp_compr_copy(struct snd_compr_stream *cstream,
+ char __user *buf, size_t count)
+{
+ struct tegra210_adsp_compr_rtd *prtd = cstream->runtime->private_data;
+ struct snd_compr_runtime *runtime = cstream->runtime;
+ void *dstn;
+ size_t copy;
+ u64 app_pointer;
+
+ dev_vdbg(prtd->dev, "%s : size %d", __func__, (uint32_t)count);
+
+ if (!count)
+ return 0;
+
+ app_pointer = div64_u64(runtime->total_bytes_available,
+ runtime->buffer_size);
+ app_pointer = runtime->total_bytes_available -
+ (app_pointer * runtime->buffer_size);
+ dstn = prtd->buf.area + app_pointer;
+
+ if (count < runtime->buffer_size - app_pointer) {
+ if (copy_from_user(dstn, buf, count))
+ return -EFAULT;
+ } else {
+ copy = runtime->buffer_size - app_pointer;
+ if (copy_from_user(dstn, buf, copy))
+ return -EFAULT;
+ if (copy_from_user(prtd->buf.area, buf + copy, count - copy))
+ return -EFAULT;
+ }
+ tegra210_adsp_send_pos_msg(prtd->fe_apm,
+ (runtime->total_bytes_available + count) % runtime->buffer_size, 0);
+
+ return count;
+}
+
+static int tegra210_adsp_compr_pointer(struct snd_compr_stream *cstream,
+ struct snd_compr_tstamp *tstamp)
+{
+ struct tegra210_adsp_compr_rtd *prtd = cstream->runtime->private_data;
+ struct tegra210_adsp_app *app = prtd->fe_apm;
+ nvfx_shared_state_t *shared = &app->apm->nvfx_shared_state;
+
+ tstamp->byte_offset = shared->input[0].bytes %
+ cstream->runtime->buffer_size;
+ tstamp->copied_total = shared->input[0].bytes;
+ tstamp->pcm_frames = shared->output[0].bytes / 4;
+ /* TODO : calculate IO frames correctly */
+ tstamp->pcm_io_frames = shared->output[0].bytes / 4;
+ tstamp->sampling_rate = prtd->codec.sample_rate;
+
+ /* TODO : If SRC in path do size conversion */
+
+ dev_vdbg(prtd->dev, "%s off %d copied %d pcm %d pcm io %d",
+ __func__, (int)tstamp->byte_offset, (int)tstamp->copied_total,
+ (int)tstamp->pcm_frames, (int)tstamp->pcm_io_frames);
+ return 0;
+}
+
+static int tegra210_adsp_compr_get_caps(struct snd_compr_stream *cstream,
+ struct snd_compr_caps *caps)
+{
+ if (cstream->direction == SND_COMPRESS_PLAYBACK)
+ memcpy(caps, &tegra210_adsp_compr_caps[SND_COMPRESS_PLAYBACK],
+ sizeof(struct snd_compr_caps));
+ else
+ memcpy(caps, &tegra210_adsp_compr_caps[SND_COMPRESS_CAPTURE],
+ sizeof(struct snd_compr_caps));
+
+ return 0;
+}
+
+static int tegra210_adsp_compr_codec_caps(struct snd_compr_stream *cstream,
+ struct snd_compr_codec_caps *codec_caps)
+{
+ struct tegra210_adsp_compr_rtd *prtd = cstream->runtime->private_data;
+
+ dev_vdbg(prtd->dev, "%s : codec %d", __func__, codec_caps->codec);
+
+ if (!codec_caps->codec)
+ codec_caps->codec = prtd->codec.id;
+
+ switch (codec_caps->codec) {
+ case SND_AUDIOCODEC_MP3:
+ memcpy(codec_caps, &adsp_compr_codec_caps[SND_AUDIOCODEC_MP3],
+ sizeof(struct snd_compr_codec_caps));
+ return 0;
+ case SND_AUDIOCODEC_AAC:
+ memcpy(codec_caps, &adsp_compr_codec_caps[SND_AUDIOCODEC_AAC],
+ sizeof(struct snd_compr_codec_caps));
+ return 0;
+ default:
+ dev_err(prtd->dev, "Unsupported codec %d", codec_caps->codec);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static struct snd_compr_ops tegra210_adsp_compr_ops = {
+
+ .open = tegra210_adsp_compr_open,
+ .free = tegra210_adsp_compr_free,
+ .set_params = tegra210_adsp_compr_set_params,
+ .get_params = tegra210_adsp_compr_get_params,
+ .trigger = tegra210_adsp_compr_trigger,
+ .pointer = tegra210_adsp_compr_pointer,
+ .copy = tegra210_adsp_compr_copy,
+ .get_caps = tegra210_adsp_compr_get_caps,
+ .get_codec_caps = tegra210_adsp_compr_codec_caps,
+};
+
+/* PCM APIs */
+static int tegra210_adsp_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct tegra210_adsp *adsp =
+ snd_soc_platform_get_drvdata(rtd->platform);
+ struct tegra210_adsp_pcm_rtd *prtd;
+ uint32_t fe_reg = rtd->codec_dai->id;
+ uint32_t source;
+ int i, ret = 0;
+
+ dev_vdbg(adsp->dev, "%s", __func__);
+
+ prtd = devm_kzalloc(adsp->dev, sizeof(struct tegra210_adsp_pcm_rtd),
+ GFP_KERNEL);
+ if (!prtd) {
+ dev_err(adsp->dev, "Failed to allocate adsp rtd.");
+ return -ENOMEM;
+ }
+
+ /* Find out the APM connected with ADSP-FE DAI */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ for (i = APM_IN_START; i < APM_IN_END; i++) {
+ struct tegra210_adsp_app *app = &adsp->apps[i];
+
+ source = tegra210_adsp_get_source(adsp, app->reg);
+ if (source == fe_reg) {
+ prtd->fe_apm = app;
+ break;
+ }
+ }
+ } else {
+ source = tegra210_adsp_get_source(adsp, fe_reg);
+ if (IS_APM_OUT(source)) {
+ uint32_t apm_in_reg =
+ APM_IN_START + (source - APM_OUT_START);
+ adsp->apps[apm_in_reg].msg_handler =
+ tegra210_adsp_pcm_msg_handler;
+ adsp->apps[apm_in_reg].private_data = prtd;
+ prtd->fe_apm = &adsp->apps[source];
+ }
+ }
+
+ if (!prtd->fe_apm) {
+ dev_err(adsp->dev, "No FE APM found\n");
+ devm_kfree(adsp->dev, prtd);
+ return -ENODEV;
+ }
+ prtd->fe_apm->msg_handler = tegra210_adsp_pcm_msg_handler;
+ prtd->fe_apm->private_data = prtd;
+ prtd->fe_apm->fe = 1;
+
+ /* Set HW params now that initialization is complete */
+ snd_soc_set_runtime_hwparams(substream, &adsp_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(adsp->dev, "failed to set constraint %d\n", ret);
+ return ret;
+ }
+ substream->runtime->private_data = prtd;
+ prtd->substream = substream;
+ prtd->dev = adsp->dev;
+
+ return 0;
+}
+
+static int tegra210_adsp_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct tegra210_adsp_pcm_rtd *prtd = substream->runtime->private_data;
+
+ dev_vdbg(prtd->dev, "%s", __func__);
+
+ if (prtd) {
+ prtd->fe_apm->fe = 1;
+ devm_kfree(prtd->dev, prtd);
+ }
+
+ return 0;
+}
+
+static int tegra210_adsp_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct tegra210_adsp_pcm_rtd *prtd = substream->runtime->private_data;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ int ret = 0;
+
+ dev_vdbg(prtd->dev, "%s rate %d chan %d bps %d"
+ "period size %d buffer size %d",
+ __func__, params_rate(params), params_channels(params),
+ snd_pcm_format_width(params_format(params)),
+ params_period_size(params),
+ params_buffer_bytes(params));
+
+ ret = tegra210_adsp_send_io_buffer_msg(prtd->fe_apm, buf->addr,
+ params_buffer_bytes(params),
+ TEGRA210_ADSP_MSG_FLAG_HOLD);
+ if (ret < 0)
+ return ret;
+
+ ret = tegra210_adsp_send_period_size_msg(prtd->fe_apm,
+ params_period_size(params),
+ TEGRA210_ADSP_MSG_FLAG_HOLD);
+ if (ret < 0)
+ return ret;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ return 0;
+}
+
+static int tegra210_adsp_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+static int tegra210_adsp_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct tegra210_adsp_pcm_rtd *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ dev_vdbg(prtd->dev, "%s : state %d", __func__, cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = tegra210_adsp_send_state_msg(prtd->fe_apm,
+ nvfx_state_active, 0);
+ if (ret < 0) {
+ dev_err(prtd->dev, "Failed to set state");
+ return ret;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ ret = tegra210_adsp_send_state_msg(prtd->fe_apm,
+ nvfx_state_inactive, 0);
+ if (ret < 0) {
+ dev_err(prtd->dev, "Failed to set state");
+ return ret;
+ }
+
+ ret = tegra210_adsp_send_reset_msg(prtd->fe_apm, 0);
+ if (ret < 0) {
+ dev_err(prtd->dev, "Failed to reset");
+ return ret;
+ }
+ break;
+ default:
+ dev_err(prtd->dev, "Unsupported state.");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tegra210_adsp_pcm_ack(struct snd_pcm_substream *substream)
+{
+ struct tegra210_adsp_pcm_rtd *prtd = substream->runtime->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ size_t pos;
+ int ret = 0;
+
+ dev_vdbg(prtd->dev, "%s %d", __func__, (int)runtime->control->appl_ptr);
+
+ pos = frames_to_bytes(runtime,
+ runtime->control->appl_ptr % runtime->buffer_size);
+ ret = tegra210_adsp_send_pos_msg(prtd->fe_apm, pos, 0);
+ if (ret < 0) {
+ dev_err(prtd->dev, "Failed to send write position.");
+ return ret;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t tegra210_adsp_pcm_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct tegra210_adsp_pcm_rtd *prtd = substream->runtime->private_data;
+ struct tegra210_adsp_app *app = prtd->fe_apm;
+ size_t bytes, pos;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ bytes = app->apm->nvfx_shared_state.output[0].bytes;
+ else
+ bytes = app->apm->nvfx_shared_state.input[0].bytes;
+
+ pos = bytes % frames_to_bytes(substream->runtime,
+ substream->runtime->buffer_size);
+
+ /* TODO : If SRC in path do size conversion */
+
+ dev_vdbg(prtd->dev, "%s bytes %zu position %zu", __func__, bytes, pos);
+ return bytes_to_frames(substream->runtime, pos);
+}
+
+static struct snd_pcm_ops tegra210_adsp_pcm_ops = {
+ .open = tegra210_adsp_pcm_open,
+ .close = tegra210_adsp_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = tegra210_adsp_pcm_hw_params,
+ .hw_free = tegra210_adsp_pcm_hw_free,
+ .trigger = tegra210_adsp_pcm_trigger,
+ .pointer = tegra210_adsp_pcm_pointer,
+ .ack = tegra210_adsp_pcm_ack,
+};
+
+static int tegra210_adsp_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+#if ENABLE_ADSP
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm *pcm = rtd->pcm;
+ int ret = 0;
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ struct snd_pcm_substream *substream =
+ pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+
+ ret = tegra210_adsp_preallocate_dma_buffer(card->dev,
+ adsp_pcm_hardware.buffer_bytes_max,
+ &substream->dma_buffer);
+
+ if (ret)
+ return ret;
+ }
+
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+ struct snd_pcm_substream *substream =
+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+
+ ret = tegra210_adsp_preallocate_dma_buffer(card->dev,
+ adsp_pcm_hardware.buffer_bytes_max,
+ &substream->dma_buffer);
+ if (ret)
+ goto err;
+ }
+
+ return 0;
+
+err:
+ tegra210_adsp_deallocate_dma_buffer(
+ &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer);
+ return ret;
+#else
+ return 0;
+#endif
+}
+
+static void tegra210_adsp_pcm_free(struct snd_pcm *pcm)
+{
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ int stream = SNDRV_PCM_STREAM_PLAYBACK;
+
+ tegra210_adsp_deallocate_dma_buffer(
+ &pcm->streams[stream].substream->dma_buffer);
+ }
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ int stream = SNDRV_PCM_STREAM_CAPTURE;
+
+ tegra210_adsp_deallocate_dma_buffer(
+ &pcm->streams[stream].substream->dma_buffer);
+ }
+}
+
+static int tegra210_adsp_pcm_probe(struct snd_soc_platform *platform)
+{
+ platform->dapm.idle_bias_off = 1;
+ return 0;
+}
+
+/* ADSP-ADMAIF codec driver HW-params. Used for configuring ADMA */
+static int tegra210_adsp_admaif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct tegra210_adsp *adsp = snd_soc_dai_get_drvdata(dai);
+ struct tegra210_adsp_app *app;
+ nvfx_adma_init_params_t adma_params;
+ uint32_t be_reg = dai->id;
+ uint32_t admaif_id = be_reg - ADSP_ADMAIF_START + 1;
+ uint32_t source;
+ int i, ret;
+
+ dev_vdbg(adsp->dev, "%s : stream %d admaif %d\n",
+ __func__, substream->stream, admaif_id);
+
+ adma_params.adma_channel = find_first_zero_bit(adsp->adma_usage,
+ TEGRA210_ADSP_ADMA_CHANNEL_COUNT);
+ if (adma_params.adma_channel >= TEGRA210_ADSP_ADMA_CHANNEL_COUNT) {
+ dev_err(adsp->dev, "All ADMA channels are busy");
+ return -EBUSY;
+ }
+ __set_bit(adma_params.adma_channel, adsp->adma_usage);
+
+ adma_params.adma_channel += TEGRA210_ADSP_ADMA_CHANNEL_START;
+ adma_params.mode = ADMA_MODE_CONTINUOUS;
+ adma_params.ahub_channel = admaif_id;
+ adma_params.periods = 2; /* We need ping-pong buffers for ADMA */
+
+ /* Set DMA params connected with ADSP-BE */
+ /* As a COCEC DAI, CAPTURE is transmit */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ app = &adsp->apps[be_reg];
+ source = tegra210_adsp_get_source(adsp, app->reg);
+
+ app = &adsp->apps[source];
+ if (!IS_APM_OUT(app->reg))
+ return 0;
+
+ source = tegra210_adsp_get_source(adsp, app->reg);
+ app = &adsp->apps[source];
+ if (!IS_ADMA(app->reg))
+ return 0;
+
+ app->adma_chan = adma_params.adma_channel;
+ adma_params.direction = ADMA_MEMORY_TO_AHUB;
+ adma_params.event.pvoid = app->apm->output_event.pvoid;
+
+ ret = tegra210_adsp_adma_params_msg(app, &adma_params, 0);
+ if (ret < 0) {
+ dev_err(adsp->dev, "ADMA params msg failed. %d.", ret);
+ return ret;
+ }
+ } else {
+ for (i = ADMA_START; i < ADMA_END; i++) {
+ app = &adsp->apps[i];
+ source = tegra210_adsp_get_source(adsp, app->reg);
+ if (!IS_APM_IN(source))
+ continue;
+
+ app = &adsp->apps[source];
+ source = tegra210_adsp_get_source(adsp, app->reg);
+ if (source != be_reg)
+ continue;
+
+ app = &adsp->apps[i];
+ app->adma_chan = adma_params.adma_channel;
+ adma_params.direction = ADMA_AHUB_TO_MEMORY;
+ adma_params.event.pvoid = app->apm->input_event.pvoid;
+
+ ret = tegra210_adsp_adma_params_msg(app,
+ &adma_params, 0);
+ if (ret < 0) {
+ dev_err(adsp->dev, "ADMA params msg failed");
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
+
+/* ADSP platform driver read/write call-back */
+static unsigned int tegra210_adsp_read(struct snd_soc_platform *platform,
+ unsigned int reg)
+{
+ struct tegra210_adsp *adsp = snd_soc_platform_get_drvdata(platform);
+
+ dev_vdbg(adsp->dev, "%s [0x%x] -> 0x%x\n", __func__, reg,
+ tegra210_adsp_reg_read(adsp, reg));
+
+ return tegra210_adsp_reg_read(adsp, reg);
+}
+
+static int tegra210_adsp_write(struct snd_soc_platform *platform,
+ unsigned int reg,
+ unsigned int val)
+{
+ struct tegra210_adsp *adsp = snd_soc_platform_get_drvdata(platform);
+
+ dev_vdbg(adsp->dev, "%s [0x%x] -> 0x%x\n", __func__, reg, val);
+
+ tegra210_adsp_reg_write(adsp, reg, val);
+ return 0;
+}
+
+/* DAPM ENUM MUX get/put callbacks */
+static int tegra210_adsp_mux_get(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 snd_soc_platform *platform = widget->platform;
+ struct soc_enum *e =
+ (struct soc_enum *)kcontrol->private_value;
+ struct tegra210_adsp *adsp = snd_soc_platform_get_drvdata(platform);
+ uint32_t val = tegra210_adsp_reg_read(adsp, e->reg);
+
+ ucontrol->value.integer.value[0] =
+ (val & TEGRA210_ADSP_WIDGET_SOURCE_MASK) >> e->shift_l;
+ return 0;
+}
+
+static int tegra210_adsp_mux_put(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 *w = wlist->widgets[0];
+ struct snd_soc_platform *platform = w->platform;
+ uint32_t val = ucontrol->value.enumerated.item[0];
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ struct tegra210_adsp *adsp = snd_soc_platform_get_drvdata(platform);
+ struct tegra210_adsp_app *app;
+ uint32_t cur_val = 0;
+ int ret = 0;
+
+ if (!adsp->init_done)
+ return -ENODEV;
+
+ /* Init or de-init app based on connection */
+ if (IS_ADSP_APP(e->reg)) {
+ app = &adsp->apps[e->reg];
+ cur_val = tegra210_adsp_get_source(adsp, e->reg);
+ if (cur_val != val)
+ app->connect = 0;
+
+ if (val == TEGRA210_ADSP_NONE) {
+ tegra210_adsp_app_deinit(adsp, app);
+ } else {
+ ret = tegra210_adsp_app_init(adsp, app);
+ if (ret < 0) {
+ dev_err(adsp->dev, "Failed to init app.");
+ return -ENODEV;
+ }
+ }
+ }
+ tegra210_adsp_reg_update_bits(adsp, e->reg,
+ TEGRA210_ADSP_WIDGET_SOURCE_MASK, val << e->shift_l);
+ tegra210_adsp_update_connection(adsp);
+
+ snd_soc_dapm_mux_update_power(w, kcontrol, val, e);
+ return 1;
+}
+
+/* ALSA control get/put call-back implementation */
+static int tegra210_adsp_init_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_platform *platform = snd_kcontrol_chip(kcontrol);
+ struct tegra210_adsp *adsp = snd_soc_platform_get_drvdata(platform);
+
+ ucontrol->value.enumerated.item[0] = adsp->init_done;
+ return 0;
+}
+
+static int tegra210_adsp_init_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_platform *platform = snd_kcontrol_chip(kcontrol);
+ struct tegra210_adsp *adsp = snd_soc_platform_get_drvdata(platform);
+ int init = ucontrol->value.enumerated.item[0];
+ int ret = 0;
+
+ if (init == adsp->init_done)
+ return 0;
+
+ if (init) {
+ ret = tegra210_adsp_init(adsp);
+ if (ret < 0) {
+ dev_err(adsp->dev, "Failed to init ADSP.");
+ return ret;
+ }
+ } else {
+ tegra210_adsp_deinit(adsp);
+ }
+
+ return 1;
+}
+
+/* DAPM widget event */
+static int tegra210_adsp_widget_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_platform *platform = w->platform;
+ struct tegra210_adsp *adsp = snd_soc_platform_get_drvdata(platform);
+ struct tegra210_adsp_app *app;
+
+ if (!IS_ADSP_APP(w->reg))
+ return 0;
+
+ app = &adsp->apps[w->reg];
+ /* For FE apm state change will be handled from trigger call back */
+ if (app->fe)
+ return 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ if (IS_APM_IN(w->reg))
+ tegra210_adsp_send_state_msg(app, nvfx_state_active, 0);
+ } else {
+ if (IS_APM_IN(w->reg)) {
+ tegra210_adsp_send_state_msg(app, nvfx_state_inactive,
+ TEGRA210_ADSP_MSG_FLAG_HOLD);
+ tegra210_adsp_send_reset_msg(app, 0);
+ } else if (IS_ADMA(w->reg)) {
+ __clear_bit(app->adma_chan -
+ TEGRA210_ADSP_ADMA_CHANNEL_START,
+ adsp->adma_usage);
+ }
+ }
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops tegra210_adsp_admaif_dai_ops = {
+ .hw_params = tegra210_adsp_admaif_hw_params,
+};
+
+static struct snd_soc_dai_driver tegra210_adsp_dai[] = {
+ {
+ .name = "ADSP PCM",
+ .playback = {
+ .stream_name = "ADSP PCM Receive",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "ADSP PCM Transmit",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+ {
+ .name = "ADSP COMPR1",
+ .compress_dai = 1,
+ .playback = {
+ .stream_name = "ADSP COMPR1 Receive",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+ {
+ .name = "ADSP COMPR2",
+ .compress_dai = 1,
+ .playback = {
+ .stream_name = "ADSP COMPR2 Receive",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+};
+
+#define ADSP_FE_CODEC_DAI(idx) \
+ { \
+ .name = "ADSP-FE" #idx, \
+ .id = ADSP_FE_START + (idx - 1), \
+ .playback = { \
+ .stream_name = "ADSP-FE" #idx " Receive",\
+ .channels_min = 1, \
+ .channels_max = 2, \
+ .rates = SNDRV_PCM_RATE_8000_48000, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, \
+ }, \
+ .capture = { \
+ .stream_name = "ADSP-FE" #idx " Transmit",\
+ .channels_min = 1, \
+ .channels_max = 2, \
+ .rates = SNDRV_PCM_RATE_8000_48000, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, \
+ }, \
+ }
+
+#define ADSP_ADMAIF_CODEC_DAI(idx) \
+ { \
+ .name = "ADSP-ADMAIF" #idx, \
+ .id = ADSP_ADMAIF_START + (idx - 1), \
+ .playback = { \
+ .stream_name = "ADSP-ADMAIF" #idx " Receive", \
+ .channels_min = 1, \
+ .channels_max = 2, \
+ .rates = SNDRV_PCM_RATE_8000_48000, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, \
+ }, \
+ .capture = { \
+ .stream_name = "ADSP-ADMAIF" #idx " Transmit",\
+ .channels_min = 1, \
+ .channels_max = 2, \
+ .rates = SNDRV_PCM_RATE_8000_48000, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, \
+ }, \
+ .ops = &tegra210_adsp_admaif_dai_ops, \
+ }
+
+static struct snd_soc_dai_driver tegra210_adsp_codec_dai[] = {
+ ADSP_FE_CODEC_DAI(1),
+ ADSP_FE_CODEC_DAI(2),
+ ADSP_FE_CODEC_DAI(3),
+ ADSP_FE_CODEC_DAI(4),
+ ADSP_FE_CODEC_DAI(5),
+ ADSP_ADMAIF_CODEC_DAI(1),
+ ADSP_ADMAIF_CODEC_DAI(2),
+ ADSP_ADMAIF_CODEC_DAI(3),
+ ADSP_ADMAIF_CODEC_DAI(4),
+ ADSP_ADMAIF_CODEC_DAI(5),
+ ADSP_ADMAIF_CODEC_DAI(6),
+ ADSP_ADMAIF_CODEC_DAI(7),
+ ADSP_ADMAIF_CODEC_DAI(8),
+ ADSP_ADMAIF_CODEC_DAI(9),
+ ADSP_ADMAIF_CODEC_DAI(10),
+};
+
+/* This array is linked with tegra210_adsp_virt_widgets enum defines. Any thing
+ changed in enum define should be also reflected here and vice-versa */
+static const char * const tegra210_adsp_mux_texts[] = {
+ "None",
+ "ADSP-FE1",
+ "ADSP-FE2",
+ "ADSP-FE3",
+ "ADSP-FE4",
+ "ADSP-FE5",
+ "ADSP-ADMAIF1",
+ "ADSP-ADMAIF2",
+ "ADSP-ADMAIF3",
+ "ADSP-ADMAIF4",
+ "ADSP-ADMAIF5",
+ "ADSP-ADMAIF6",
+ "ADSP-ADMAIF7",
+ "ADSP-ADMAIF8",
+ "ADSP-ADMAIF9",
+ "ADSP-ADMAIF10",
+ "APM-IN1",
+ "APM-IN2",
+ "APM-IN3",
+ "APM-IN4",
+ "APM-IN5",
+ "APM-IN6",
+ "APM-IN7",
+ "APM-IN8",
+ "APM-OUT1",
+ "APM-OUT2",
+ "APM-OUT3",
+ "APM-OUT4",
+ "APM-OUT5",
+ "APM-OUT6",
+ "APM-OUT7",
+ "APM-OUT8",
+ "ADMA1",
+ "ADMA2",
+ "ADMA3",
+ "ADMA4",
+ "MP3-DEC1",
+ "MP3-DEC2",
+ "AAC-DEC1",
+ "AAC-DEC2",
+ "SPKPROT-SW",
+};
+
+#define ADSP_MUX_ENUM_CTRL_DECL(ename, reg) \
+ SOC_ENUM_SINGLE_DECL(ename##_enum, reg, \
+ TEGRA210_ADSP_WIDGET_SOURCE_SHIFT, \
+ tegra210_adsp_mux_texts); \
+ static const struct snd_kcontrol_new ename##_ctrl = \
+ SOC_DAPM_ENUM_EXT("ADSP Route", ename##_enum, \
+ tegra210_adsp_mux_get, tegra210_adsp_mux_put)
+
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe1, TEGRA210_ADSP_FRONT_END1);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe2, TEGRA210_ADSP_FRONT_END2);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe3, TEGRA210_ADSP_FRONT_END3);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe4, TEGRA210_ADSP_FRONT_END4);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe5, TEGRA210_ADSP_FRONT_END5);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif1, TEGRA210_ADSP_ADMAIF1);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif2, TEGRA210_ADSP_ADMAIF2);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif3, TEGRA210_ADSP_ADMAIF3);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif4, TEGRA210_ADSP_ADMAIF4);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif5, TEGRA210_ADSP_ADMAIF5);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif6, TEGRA210_ADSP_ADMAIF6);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif7, TEGRA210_ADSP_ADMAIF7);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif8, TEGRA210_ADSP_ADMAIF8);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif9, TEGRA210_ADSP_ADMAIF9);
+static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif10, TEGRA210_ADSP_ADMAIF10);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_in1, TEGRA210_ADSP_APM_IN1);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_in2, TEGRA210_ADSP_APM_IN2);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_in3, TEGRA210_ADSP_APM_IN3);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_in4, TEGRA210_ADSP_APM_IN4);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_in5, TEGRA210_ADSP_APM_IN5);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_in6, TEGRA210_ADSP_APM_IN6);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_in7, TEGRA210_ADSP_APM_IN7);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_in8, TEGRA210_ADSP_APM_IN8);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_out1, TEGRA210_ADSP_APM_OUT1);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_out2, TEGRA210_ADSP_APM_OUT2);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_out3, TEGRA210_ADSP_APM_OUT3);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_out4, TEGRA210_ADSP_APM_OUT4);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_out5, TEGRA210_ADSP_APM_OUT5);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_out6, TEGRA210_ADSP_APM_OUT6);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_out7, TEGRA210_ADSP_APM_OUT7);
+static ADSP_MUX_ENUM_CTRL_DECL(apm_out8, TEGRA210_ADSP_APM_OUT8);
+static ADSP_MUX_ENUM_CTRL_DECL(adma1, TEGRA210_ADSP_PLUGIN_ADMA1);
+static ADSP_MUX_ENUM_CTRL_DECL(adma2, TEGRA210_ADSP_PLUGIN_ADMA2);
+static ADSP_MUX_ENUM_CTRL_DECL(adma3, TEGRA210_ADSP_PLUGIN_ADMA3);
+static ADSP_MUX_ENUM_CTRL_DECL(adma4, TEGRA210_ADSP_PLUGIN_ADMA4);
+static ADSP_MUX_ENUM_CTRL_DECL(mp3_dec1, TEGRA210_ADSP_PLUGIN_MP3_DEC1);
+static ADSP_MUX_ENUM_CTRL_DECL(mp3_dec2, TEGRA210_ADSP_PLUGIN_MP3_DEC2);
+static ADSP_MUX_ENUM_CTRL_DECL(aac_dec1, TEGRA210_ADSP_PLUGIN_AAC_DEC1);
+static ADSP_MUX_ENUM_CTRL_DECL(aac_dec2, TEGRA210_ADSP_PLUGIN_AAC_DEC2);
+static ADSP_MUX_ENUM_CTRL_DECL(spkprot, TEGRA210_ADSP_PLUGIN_SPKPROT);
+
+#define ADSP_EP_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_MUX(sname " MUX", SND_SOC_NOPM, 0, 0, &ename##_ctrl)
+
+#define ADSP_WIDGETS(sname, ename, reg) \
+ SND_SOC_DAPM_AIF_OUT_E(sname " TX", NULL, 0, reg, \
+ TEGRA210_ADSP_WIDGET_EN_SHIFT, 0, tegra210_adsp_widget_event, \
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), \
+ SND_SOC_DAPM_MUX(sname " MUX", SND_SOC_NOPM, 0, 0, &ename##_ctrl)
+
+static const struct snd_soc_dapm_widget tegra210_adsp_widgets[] = {
+ ADSP_EP_WIDGETS("ADSP-FE1", adsp_fe1),
+ ADSP_EP_WIDGETS("ADSP-FE2", adsp_fe2),
+ ADSP_EP_WIDGETS("ADSP-FE3", adsp_fe3),
+ ADSP_EP_WIDGETS("ADSP-FE4", adsp_fe4),
+ ADSP_EP_WIDGETS("ADSP-FE5", adsp_fe5),
+ ADSP_EP_WIDGETS("ADSP-ADMAIF1", adsp_admaif1),
+ ADSP_EP_WIDGETS("ADSP-ADMAIF2", adsp_admaif2),
+ ADSP_EP_WIDGETS("ADSP-ADMAIF3", adsp_admaif3),
+ ADSP_EP_WIDGETS("ADSP-ADMAIF4", adsp_admaif4),
+ ADSP_EP_WIDGETS("ADSP-ADMAIF5", adsp_admaif5),
+ ADSP_EP_WIDGETS("ADSP-ADMAIF6", adsp_admaif6),
+ ADSP_EP_WIDGETS("ADSP-ADMAIF7", adsp_admaif7),
+ ADSP_EP_WIDGETS("ADSP-ADMAIF8", adsp_admaif8),
+ ADSP_EP_WIDGETS("ADSP-ADMAIF9", adsp_admaif9),
+ ADSP_EP_WIDGETS("ADSP-ADMAIF10", adsp_admaif10),
+ ADSP_WIDGETS("APM-IN1", apm_in1, TEGRA210_ADSP_APM_IN1),
+ ADSP_WIDGETS("APM-IN2", apm_in2, TEGRA210_ADSP_APM_IN2),
+ ADSP_WIDGETS("APM-IN3", apm_in3, TEGRA210_ADSP_APM_IN3),
+ ADSP_WIDGETS("APM-IN4", apm_in4, TEGRA210_ADSP_APM_IN4),
+ ADSP_WIDGETS("APM-IN5", apm_in5, TEGRA210_ADSP_APM_IN5),
+ ADSP_WIDGETS("APM-IN6", apm_in6, TEGRA210_ADSP_APM_IN6),
+ ADSP_WIDGETS("APM-IN7", apm_in7, TEGRA210_ADSP_APM_IN7),
+ ADSP_WIDGETS("APM-IN8", apm_in8, TEGRA210_ADSP_APM_IN8),
+ ADSP_WIDGETS("APM-OUT1", apm_out1, TEGRA210_ADSP_APM_OUT1),
+ ADSP_WIDGETS("APM-OUT2", apm_out2, TEGRA210_ADSP_APM_OUT2),
+ ADSP_WIDGETS("APM-OUT3", apm_out3, TEGRA210_ADSP_APM_OUT3),
+ ADSP_WIDGETS("APM-OUT4", apm_out4, TEGRA210_ADSP_APM_OUT4),
+ ADSP_WIDGETS("APM-OUT5", apm_out5, TEGRA210_ADSP_APM_OUT5),
+ ADSP_WIDGETS("APM-OUT6", apm_out6, TEGRA210_ADSP_APM_OUT6),
+ ADSP_WIDGETS("APM-OUT7", apm_out7, TEGRA210_ADSP_APM_OUT7),
+ ADSP_WIDGETS("APM-OUT8", apm_out8, TEGRA210_ADSP_APM_OUT8),
+ ADSP_WIDGETS("ADMA1", adma1, TEGRA210_ADSP_PLUGIN_ADMA1),
+ ADSP_WIDGETS("ADMA2", adma2, TEGRA210_ADSP_PLUGIN_ADMA2),
+ ADSP_WIDGETS("ADMA3", adma3, TEGRA210_ADSP_PLUGIN_ADMA3),
+ ADSP_WIDGETS("ADMA4", adma4, TEGRA210_ADSP_PLUGIN_ADMA4),
+ ADSP_WIDGETS("MP3-DEC1", mp3_dec1, TEGRA210_ADSP_PLUGIN_MP3_DEC1),
+ ADSP_WIDGETS("MP3-DEC2", mp3_dec2, TEGRA210_ADSP_PLUGIN_MP3_DEC2),
+ ADSP_WIDGETS("AAC-DEC1", aac_dec1, TEGRA210_ADSP_PLUGIN_AAC_DEC1),
+ ADSP_WIDGETS("AAC-DEC2", aac_dec2, TEGRA210_ADSP_PLUGIN_AAC_DEC2),
+ ADSP_WIDGETS("SPKPROT-SW", spkprot, TEGRA210_ADSP_PLUGIN_SPKPROT),
+};
+
+#define ADSP_EP_ROUTES(name) \
+ { name " MUX", "ADSP-FE1", "ADSP-FE1 RX"}, \
+ { name " MUX", "ADSP-FE2", "ADSP-FE2 RX"}, \
+ { name " MUX", "ADSP-FE3", "ADSP-FE3 RX"}, \
+ { name " MUX", "ADSP-FE4", "ADSP-FE4 RX"}, \
+ { name " MUX", "ADSP-FE5", "ADSP-FE5 RX"}, \
+ { name " MUX", "ADSP-ADMAIF1", "ADSP-ADMAIF1 RX"}, \
+ { name " MUX", "ADSP-ADMAIF2", "ADSP-ADMAIF2 RX"}, \
+ { name " MUX", "ADSP-ADMAIF3", "ADSP-ADMAIF3 RX"}, \
+ { name " MUX", "ADSP-ADMAIF4", "ADSP-ADMAIF4 RX"}, \
+ { name " MUX", "ADSP-ADMAIF5", "ADSP-ADMAIF5 RX"}, \
+ { name " MUX", "ADSP-ADMAIF6", "ADSP-ADMAIF6 RX"}, \
+ { name " MUX", "ADSP-ADMAIF7", "ADSP-ADMAIF7 RX"}, \
+ { name " MUX", "ADSP-ADMAIF8", "ADSP-ADMAIF8 RX"}, \
+ { name " MUX", "ADSP-ADMAIF9", "ADSP-ADMAIF9 RX"}, \
+ { name " MUX", "ADSP-ADMAIF10", "ADSP-ADMAIF10 RX"}
+
+#define ADSP_APM_IN_ROUTES(name) \
+ { name " MUX", "APM-IN1", "APM-IN1 TX"}, \
+ { name " MUX", "APM-IN2", "APM-IN2 TX"}, \
+ { name " MUX", "APM-IN3", "APM-IN3 TX"}, \
+ { name " MUX", "APM-IN4", "APM-IN4 TX"}, \
+ { name " MUX", "APM-IN5", "APM-IN5 TX"}, \
+ { name " MUX", "APM-IN6", "APM-IN6 TX"}, \
+ { name " MUX", "APM-IN7", "APM-IN7 TX"}, \
+ { name " MUX", "APM-IN8", "APM-IN8 TX"}
+
+#define ADSP_APM_OUT_ROUTES(name) \
+ { name " MUX", "APM-OUT1", "APM-OUT1 TX"}, \
+ { name " MUX", "APM-OUT2", "APM-OUT2 TX"}, \
+ { name " MUX", "APM-OUT3", "APM-OUT3 TX"}, \
+ { name " MUX", "APM-OUT4", "APM-OUT4 TX"}, \
+ { name " MUX", "APM-OUT5", "APM-OUT5 TX"}, \
+ { name " MUX", "APM-OUT6", "APM-OUT6 TX"}, \
+ { name " MUX", "APM-OUT7", "APM-OUT7 TX"}, \
+ { name " MUX", "APM-OUT8", "APM-OUT8 TX"}
+
+#define ADSP_ADMA_ROUTES(name) \
+ { name " MUX", "ADMA1", "ADMA1 TX"}, \
+ { name " MUX", "ADMA2", "ADMA2 TX"}, \
+ { name " MUX", "ADMA3", "ADMA3 TX"}, \
+ { name " MUX", "ADMA4", "ADMA4 TX"}
+
+#define ADSP_DEC_ROUTES(name) \
+ { name " MUX", "MP3-DEC1", "MP3-DEC1 TX"}, \
+ { name " MUX", "MP3-DEC2", "MP3-DEC2 TX"}, \
+ { name " MUX", "AAC-DEC1", "AAC-DEC1 TX"}, \
+ { name " MUX", "AAC-DEC2", "AAC-DEC2 TX"}
+
+#define ADSP_SPKPROT_ROUTES(name) \
+ { name " MUX", "SPKPROT-SW", "SPKPROT-SW TX"}
+
+#define ADSP_EP_MUX_ROUTES(name) \
+ { name " RX", NULL, name " Receive"}, \
+ { name " Transmit", NULL, name " TX"}, \
+ { name " TX", NULL, name " MUX"}, \
+ ADSP_APM_OUT_ROUTES(name)
+
+#define ADSP_APM_IN_MUX_ROUTES(name) \
+ { name " TX", NULL, name " MUX"}, \
+ ADSP_EP_ROUTES(name), \
+ ADSP_APM_OUT_ROUTES(name)
+
+#define ADSP_APM_OUT_MUX_ROUTES(name) \
+ { name " TX", NULL, name " MUX"}, \
+ ADSP_ADMA_ROUTES(name), \
+ ADSP_DEC_ROUTES(name)
+
+#define ADSP_DEC_MUX_ROUTES(name) \
+ { name " TX", NULL, name " MUX"}, \
+ ADSP_APM_IN_ROUTES(name)
+
+#define ADSP_ADMA_MUX_ROUTES(name) \
+ { name " TX", NULL, name " MUX"}, \
+ ADSP_APM_IN_ROUTES(name), \
+ ADSP_DEC_ROUTES(name), \
+ ADSP_SPKPROT_ROUTES(name)
+
+#define ADSP_SPKPROT_MUX_ROUTES(name) \
+ { name " TX", NULL, name " MUX"}, \
+ ADSP_APM_IN_ROUTES(name), \
+ ADSP_DEC_ROUTES(name), \
+ ADSP_ADMA_ROUTES(name)
+
+static const struct snd_soc_dapm_route tegra210_adsp_routes[] = {
+ ADSP_EP_MUX_ROUTES("ADSP-FE1"),
+ ADSP_EP_MUX_ROUTES("ADSP-FE2"),
+ ADSP_EP_MUX_ROUTES("ADSP-FE3"),
+ ADSP_EP_MUX_ROUTES("ADSP-FE4"),
+ ADSP_EP_MUX_ROUTES("ADSP-FE5"),
+ ADSP_EP_MUX_ROUTES("ADSP-ADMAIF1"),
+ ADSP_EP_MUX_ROUTES("ADSP-ADMAIF2"),
+ ADSP_EP_MUX_ROUTES("ADSP-ADMAIF3"),
+ ADSP_EP_MUX_ROUTES("ADSP-ADMAIF4"),
+ ADSP_EP_MUX_ROUTES("ADSP-ADMAIF5"),
+ ADSP_EP_MUX_ROUTES("ADSP-ADMAIF6"),
+ ADSP_EP_MUX_ROUTES("ADSP-ADMAIF7"),
+ ADSP_EP_MUX_ROUTES("ADSP-ADMAIF8"),
+ ADSP_EP_MUX_ROUTES("ADSP-ADMAIF9"),
+ ADSP_EP_MUX_ROUTES("ADSP-ADMAIF10"),
+
+ ADSP_APM_IN_MUX_ROUTES("APM-IN1"),
+ ADSP_APM_IN_MUX_ROUTES("APM-IN2"),
+ ADSP_APM_IN_MUX_ROUTES("APM-IN3"),
+ ADSP_APM_IN_MUX_ROUTES("APM-IN4"),
+ ADSP_APM_IN_MUX_ROUTES("APM-IN5"),
+ ADSP_APM_IN_MUX_ROUTES("APM-IN6"),
+ ADSP_APM_IN_MUX_ROUTES("APM-IN7"),
+ ADSP_APM_IN_MUX_ROUTES("APM-IN8"),
+
+ ADSP_APM_OUT_MUX_ROUTES("APM-OUT1"),
+ ADSP_APM_OUT_MUX_ROUTES("APM-OUT2"),
+ ADSP_APM_OUT_MUX_ROUTES("APM-OUT3"),
+ ADSP_APM_OUT_MUX_ROUTES("APM-OUT4"),
+ ADSP_APM_OUT_MUX_ROUTES("APM-OUT5"),
+ ADSP_APM_OUT_MUX_ROUTES("APM-OUT6"),
+ ADSP_APM_OUT_MUX_ROUTES("APM-OUT7"),
+ ADSP_APM_OUT_MUX_ROUTES("APM-OUT8"),
+
+ ADSP_ADMA_MUX_ROUTES("ADMA1"),
+ ADSP_ADMA_MUX_ROUTES("ADMA2"),
+ ADSP_ADMA_MUX_ROUTES("ADMA3"),
+ ADSP_ADMA_MUX_ROUTES("ADMA4"),
+
+ ADSP_DEC_MUX_ROUTES("MP3-DEC1"),
+ ADSP_DEC_MUX_ROUTES("MP3-DEC2"),
+ ADSP_DEC_MUX_ROUTES("AAC-DEC1"),
+ ADSP_DEC_MUX_ROUTES("AAC-DEC2"),
+
+ ADSP_SPKPROT_MUX_ROUTES("SPKPROT-SW"),
+};
+
+static const struct snd_kcontrol_new tegra210_adsp_controls[] = {
+ SOC_SINGLE_BOOL_EXT("ADSP init", 0,
+ tegra210_adsp_init_get, tegra210_adsp_init_put),
+};
+
+static const struct snd_soc_component_driver tegra210_adsp_component = {
+ .name = "tegra210-adsp",
+};
+
+static int tegra210_adsp_codec_probe(struct snd_soc_codec *codec)
+{
+ return 0;
+}
+
+static struct snd_soc_codec_driver tegra210_adsp_codec = {
+ .probe = tegra210_adsp_codec_probe,
+ .idle_bias_off = 1,
+};
+
+static struct snd_soc_platform_driver tegra210_adsp_platform = {
+ .ops = &tegra210_adsp_pcm_ops,
+ .compr_ops = &tegra210_adsp_compr_ops,
+ .pcm_new = tegra210_adsp_pcm_new,
+ .pcm_free = tegra210_adsp_pcm_free,
+ .probe = tegra210_adsp_pcm_probe,
+ .read = tegra210_adsp_read,
+ .write = tegra210_adsp_write,
+ .dapm_widgets = tegra210_adsp_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra210_adsp_widgets),
+ .dapm_routes = tegra210_adsp_routes,
+ .num_dapm_routes = ARRAY_SIZE(tegra210_adsp_routes),
+ .controls = tegra210_adsp_controls,
+ .num_controls = ARRAY_SIZE(tegra210_adsp_controls),
+};
+
+static u64 tegra_dma_mask = DMA_BIT_MASK(32);
+static int tegra210_adsp_audio_platform_probe(struct platform_device *pdev)
+{
+ struct tegra210_adsp *adsp;
+ int i, j, ret = 0;
+
+ pr_info("tegra210_adsp_audio_platform_probe: platform probe started\n");
+
+ adsp = devm_kzalloc(&pdev->dev, sizeof(*adsp), GFP_KERNEL);
+ if (!adsp) {
+ dev_err(&pdev->dev, "Can't allocate tegra30_adsp_ctx\n");
+ return -ENOMEM;
+ }
+ dev_set_drvdata(&pdev->dev, adsp);
+ adsp->dev = &pdev->dev;
+
+ mutex_init(&adsp->mutex);
+ pdev->dev.dma_mask = &tegra_dma_mask;
+ pdev->dev.coherent_dma_mask = tegra_dma_mask;
+
+ tegra_ape_pd_add_device(&pdev->dev);
+ pm_genpd_dev_need_save(&pdev->dev, true);
+ pm_genpd_dev_need_restore(&pdev->dev, true);
+
+ pm_runtime_enable(&pdev->dev);
+ if (!pm_runtime_enabled(&pdev->dev))
+ goto err_pm_disable;
+
+ pm_runtime_get_sync(&pdev->dev);
+ /* HACK : Should be handled through dma-engine */
+ for (i = 0; i < TEGRA210_ADSP_ADMA_CHANNEL_COUNT; i++) {
+ ret = tegra_agic_route_interrupt(
+ INT_ADMA_EOT0 + TEGRA210_ADSP_ADMA_CHANNEL_START + i,
+ TEGRA_AGIC_ADSP);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to route INT to ADSP");
+ goto err_pm_disable;
+ }
+ }
+ /* HACK end */
+ pm_runtime_put(&pdev->dev);
+
+ for (i = 0; i < TEGRA210_ADSP_VIRT_REG_MAX; i++)
+ adsp->apps[i].reg = i;
+
+ for (i = 0; i < ARRAY_SIZE(adsp_app_desc); i++) {
+ for (j = adsp_app_desc[i].reg_start;
+ j <= adsp_app_desc[i].reg_end; j++)
+ adsp->apps[j].desc = &adsp_app_desc[i];
+ }
+
+ ret = snd_soc_register_platform(&pdev->dev, &tegra210_adsp_platform);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register platform: %d\n", ret);
+ goto err_pm_disable;
+ }
+
+ ret = snd_soc_register_component(&pdev->dev, &tegra210_adsp_component,
+ tegra210_adsp_dai, ARRAY_SIZE(tegra210_adsp_dai));
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register component: %d\n", ret);
+ goto err_unregister_platform;
+ }
+
+ ret = snd_soc_register_codec(&pdev->dev, &tegra210_adsp_codec,
+ tegra210_adsp_codec_dai,
+ ARRAY_SIZE(tegra210_adsp_codec_dai));
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Could not register CODEC: %d\n", ret);
+ goto err_unregister_platform;
+ }
+
+ pr_info("tegra210_adsp_audio_platform_probe probe successfull.");
+ return 0;
+
+err_unregister_platform:
+ snd_soc_unregister_platform(&pdev->dev);
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+ tegra_ape_pd_remove_device(&pdev->dev);
+ return ret;
+}
+
+static int tegra210_adsp_audio_platform_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+ tegra_ape_pd_remove_device(&pdev->dev);
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static const struct of_device_id tegra210_adsp_audio_of_match[] = {
+ { .compatible = "nvidia,tegra210-adsp-audio", },
+ {},
+};
+
+static struct platform_driver tegra210_adsp_audio_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = tegra210_adsp_audio_of_match,
+ },
+ .probe = tegra210_adsp_audio_platform_probe,
+ .remove = tegra210_adsp_audio_platform_remove,
+};
+module_platform_driver(tegra210_adsp_audio_driver);
+
+MODULE_AUTHOR("Sumit Bhattacharya <sumitb@nvidia.com>");
+MODULE_DESCRIPTION("Tegra210 ADSP Audio driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_DEVICE_TABLE(of, tegra210_adsp_audio_of_match);
+
--- /dev/null
- .sample_rates = SNDRV_PCM_RATE_44100 |
+/*
+ * 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] = {25000, "nvavp_aud_ucode.bin" },
+ [CODEC_MP3] = {45000, "nvavp_mp3dec_ucode.bin" },
+ [CODEC_AAC] = {80000, "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;
+
+ atomic_t is_dma_allocated;
+ atomic_t 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, unsigned int is_eos);
+ void *notify_args;
+ unsigned int is_drain_called;
+};
+
+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;
+ atomic_t 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 |
++ .sample_rates = {
++ [0] = 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 = {
++ [0] = 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_RAW,
+ .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: is_dma_allocated %d",
+ __func__, atomic_read(&dma->is_dma_allocated));
+
+ if (atomic_read(&dma->is_dma_allocated) == 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;
+ atomic_set(&dma->is_dma_allocated, 1);
+
+ 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: is_dma_allocated %d",
+ __func__, atomic_read(&dma->is_dma_allocated));
+
+ if (atomic_read(&dma->is_dma_allocated) == 1) {
+ dma_release_channel(dma->chan);
+ atomic_set(&dma->is_dma_allocated, 0);
+ }
+
+ return;
+}
+
+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__,
+ atomic_read(&dma->active_count));
+
+ if (atomic_inc_return(&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__,
+ atomic_read(&dma->active_count));
+
+ if (atomic_dec_and_test(&dma->active_count))
+ 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 if (old_state == KSSTATE_RUN)
+ 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 (!stream->stream_allocated)
+ continue;
+
+ if (avp_stream->is_drain_called &&
+ (stream->source_buffer_read_position ==
+ stream->source_buffer_write_position) &&
+ (avp_stream->notification_received >=
+ stream->stream_notification_request)) {
+ /* End of stream occured and noitfy same with value 1 */
+ avp_stream->notify_cb(avp_stream->notify_args, 1);
+ tegra30_avp_stream_set_state(i, KSSTATE_STOP);
+ } else if (stream->stream_notification_request >
+ avp_stream->notification_received) {
+ avp_stream->notification_received++;
+
+ avp_stream->notify_cb(avp_stream->notify_args, 0);
+ }
+ }
+}
+
+/******************************************************************************
+ * 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);
+}
+
+/* Loopback APIs */
+static int tegra30_avp_loopback_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:entry\n", __func__);
+
+ /* 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 =
+ (uintptr_t)(params->source_buf.virt_addr);
+ stream->source_buffer_avp = params->source_buf.phys_addr;
+ stream->source_buffer_size = params->buffer_size;
+ return ret;
+}
+
+static int tegra30_avp_loopback_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:
+ stream->stream_state_target = KSSTATE_RUN;
+ return 0;
+ case SNDRV_PCM_TRIGGER_STOP:
+ stream->stream_state_target = 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;
+ return 0;
+ default:
+ dev_err(audio_avp->dev, "Unsupported state.");
+ return -EINVAL;
+ }
+}
+
+static size_t tegra30_avp_loopback_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;
+}
+
+static void tegra30_avp_loopback_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;
+}
+
+/* PCM APIs */
+static int tegra30_avp_pcm_open(int *id, char *stream)
+{
+ struct tegra30_avp_audio *audio_avp = avp_audio_ctx;
+ struct audio_engine_data *audio_engine = audio_avp->audio_engine;
+ struct tegra30_avp_stream *avp_stream;
+ 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 (strcmp(stream, "pcm") == 0) {
+ avp_stream = &audio_avp->avp_stream[1];
+ if (!audio_engine->stream[pcm_stream_id].stream_allocated) {
+ *id = pcm_stream_id;
+ audio_engine->stream[*id].stream_allocated = 1;
+ atomic_inc(&audio_avp->stream_active_count);
+ } else if (
+ !audio_engine->stream[pcm2_stream_id].stream_allocated) {
+ *id = pcm2_stream_id;
+ audio_engine->stream[*id].stream_allocated = 1;
+ atomic_inc(&audio_avp->stream_active_count);
+ } else {
+ dev_err(audio_avp->dev, "All AVP PCM streams are busy");
+ return -EBUSY;
+ }
+ } else if (strcmp(stream, "loopback") == 0) {
+ avp_stream = &audio_avp->avp_stream[0];
+ if
+ (!audio_engine->stream[loopback_stream_id].stream_allocated) {
+ dev_vdbg(audio_avp->dev,
+ "Assigning loopback id:%d\n", loopback_stream_id);
+ audio_engine->stream[*id].stream_allocated = 1;
+ *id = loopback_stream_id;
+ } else {
+ dev_err(audio_avp->dev, "AVP loopback streams is 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 =
+ (uintptr_t) (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(¶ms->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;
+ }
+ audio_avp->avp_stream[*id].is_drain_called = 0;
+ audio_engine->stream[*id].stream_allocated = 1;
+
+ atomic_inc(&audio_avp->stream_active_count);
+ 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_RAW;
+ 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);
+ ret = tegra30_avp_mem_alloc(&avp_stream->source_buf,
+ stream->source_buffer_size);
+ if (ret < 0) {
+ dev_err(audio_avp->dev, "Failed to allocate source buf memory");
+ return ret;
+ }
+
+ stream->source_buffer_system =
+ (uintptr_t) 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(¶ms->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 Offload 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;
+ struct tegra30_avp_stream *avp_stream = &audio_avp->avp_stream[id];
+
+ 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:
+ case SND_COMPR_TRIGGER_PARTIAL_DRAIN:
+ avp_stream->is_drain_called = 1;
+ 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 = (char *)(uintptr_t)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((void *)(uintptr_t)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);
+ stream->stream_allocated = 0;
+ tegra30_avp_stream_set_state(id, KSSTATE_STOP);
+
+ if (id == loopback_stream_id)
+ return;
+
+ if (atomic_dec_and_test(&audio_avp->stream_active_count)) {
+ tegra30_avp_audio_free_dma();
+ 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,
+ },
+ .loopback_ops = {
+ .stream_open = tegra30_avp_pcm_open,
+ .stream_close = tegra30_avp_stream_close,
+ .set_stream_params = tegra30_avp_loopback_set_params,
+ .set_stream_state = tegra30_avp_loopback_set_state,
+ .get_stream_position = tegra30_avp_loopback_get_position,
+ .data_ready = tegra30_avp_loopback_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_deregister_offload_ops();
+ if (audio_avp->nvavp_client)
+ 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);