/* * tegra210_adsp_virt_alt.c - Tegra ADSP audio driver * * Author: Sumit Bhattacharya * Copyright (c) 2014-2021, 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 #include #include #include #include #include #include <../arch/arm/mach-tegra/iomap.h> #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tegra_isomgr_bw_alt.h" #include "tegra_asoc_utils_alt.h" #include "tegra210_adsp_alt.h" #include "tegra210_virt_alt_admaif.h" #include "tegra_virt_alt_ivc.h" #define DRV_NAME_ADSP "tegra210-adsp-virt" /* Flag to enable/disable loading of ADSP firmware */ #define ENABLE_ADSP 1 #define NETLINK_ADSP_EVENT 31 #define NETLINK_ADSP_EVENT_GROUP 1 struct adsp_event_nlmsg { uint32_t err; uint32_t data[NVFX_MAX_CALL_PARAMS_WSIZE]; }; #define ADSP_RESPONSE_TIMEOUT 1000 /* in ms */ /* ADSP controls plugin index */ #define PLUGIN_SET_PARAMS_IDX 1 #define PLUGIN_SEND_BYTES_IDX 21 static const unsigned int tegra210_adsp_rates[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }; static const struct snd_pcm_hw_constraint_list tegra210_adsp_rate_constraints = { .count = ARRAY_SIZE(tegra210_adsp_rates), .list = tegra210_adsp_rates, }; static struct tegra210_adsp_app_desc { const char *name; const char *fw_name; const char *wt_name; uint32_t param_type; uint32_t reg_start; uint32_t reg_end; nvadsp_app_handle_t handle; } adsp_app_minimal[] = { {"apm", "nvapm.elf", NULL, SNDRV_CTL_ELEM_TYPE_NONE, APM_IN_START, APM_IN_END}, {"adma", "nvadma.elf", NULL, SNDRV_CTL_ELEM_TYPE_NONE, ADMA_START, ADMA_END}, {"adma_tx", "nvadma_tx.elf", NULL, SNDRV_CTL_ELEM_TYPE_NONE, ADMA_TX_START, ADMA_TX_END}, }; static struct tegra210_adsp_app_desc *adsp_app_desc; static unsigned int adsp_app_count; /* total number of apps initialized */ static int apm_stack_size[APM_IN_END - APM_IN_START + 1]; struct tegra210_adsp_app_read_data { int32_t data[NVFX_MAX_RAW_DATA_WSIZE]; }; /* ADSP APP specific structure */ struct tegra210_adsp_app { struct tegra210_adsp *adsp; 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 apm_mbox; struct completion *msg_complete; /* For ADSP ack wait */ struct completion *raw_msg_read_complete; struct completion *raw_msg_write_complete; 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 */ int32_t fe_playback_triggered; /* if app is playback FE, indicates whether in triggered state or inactive */ uint32_t connect:1; /* if app is connected to a source */ uint32_t priority; /* Valid for only APM app */ uint32_t min_adsp_clock; /* Min ADSP clock required in MHz */ uint32_t input_mode; /* APM input mode */ struct tegra210_adsp_app_read_data read_data; spinlock_t lock; void *private_data; int (*msg_handler)(struct tegra210_adsp_app *, apm_msg_t *); struct work_struct *override_freq_work; spinlock_t apm_msg_queue_lock; spinlock_t fe_playback_lock; }; struct tegra210_adsp_pcm_rtd { struct device *dev; struct snd_pcm_substream *substream; struct tegra210_adsp_app *fe_apm; snd_pcm_uframes_t prev_appl_ptr; }; 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_switch { uint32_t admaif_id; uint32_t allowed_fe; uint32_t active_fe; }; struct adsp_soc_data { bool is_soc_t210; uint32_t max_adma_ch; }; struct tegra210_adsp { struct device *dev; struct adsp_soc_data *soc_data; 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_BITMAP_COUNT); struct clk *ahub_clk; struct clk *ape_clk; struct clk *apb2ape_clk; struct work_struct override_freq_work; uint32_t i2s_rate; struct mutex mutex; int init_done; int adsp_started; bool is_shutdown; uint32_t adma_ch_page; uint32_t adma_ch_start; uint32_t adma_ch_cnt; struct tegra210_adsp_path { uint32_t channels; uint32_t format; uint32_t rate; } pcm_path[ADSP_FE_COUNT+1][2]; struct nvaudio_ivc_ctxt *hivc_client; int32_t fe_to_admaif_map[ADSP_FE_COUNT][2]; int32_t apm_to_admaif_map[APM_IN_END - APM_IN_START + 1][2]; struct tegra210_adsp_switch switches[MAX_ADSP_SWITCHES]; bool is_fe_set[ADSP_FE_COUNT]; spinlock_t switch_lock; struct sock *nl_sk; }; 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 | SNDRV_PCM_INFO_DRAIN_TRIGGER, .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, .channels_min = 1, .channels_max = 2, .period_bytes_min = 128, .period_bytes_max = PAGE_SIZE * 2, .periods_min = 1, .periods_max = 8, .buffer_bytes_max = PAGE_SIZE * 8, .fifo_size = 4, }; /* 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 = { [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); static int tegra210_adsp_app_default_msg_handler(struct tegra210_adsp_app *app, apm_msg_t *apm_msg); /* * 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 find Plugin app from name*/ static struct tegra210_adsp_app *tegra210_adsp_get_plugin( struct tegra210_adsp *adsp, const char *plugin_name) { struct tegra210_adsp_app *app; int i; for (i = PLUGIN_START; i <= PLUGIN_END; i++) { app = &adsp->apps[i]; if (!strcmp(app->desc->name, plugin_name)) return app; } return NULL; } /* 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; } #if IS_ENABLED(CONFIG_TEGRA210_ADMA) /* implemented in adma driver */ void tegra_adma_dump_ch_reg(void); #endif /* 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 < adsp_app_count; i++) { adsp_app_desc[i].handle = nvadsp_app_load( adsp_app_desc[i].name, adsp_app_desc[i].fw_name); if (!adsp_app_desc[i].handle) { dev_err(adsp->dev, "Failed to load app %s", adsp_app_desc[i].name); } } #if IS_ENABLED(CONFIG_TEGRA210_ADMA) /* set callback function for adsp to dump adma registers for debug */ nvadsp_set_adma_dump_reg(&tegra_adma_dump_ch_reg); #endif /* Suspend OS for now. Resume will happen via runtime pm calls */ ret = nvadsp_os_suspend(); if (ret < 0) { dev_err(adsp->dev, "Failed to suspend OS."); 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) { nvadsp_os_stop(); 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_get_raw_data_msg(apm_shared_state_t *apm, apm_raw_data_msg_t *apm_msg) { apm_msg->msgq_msg.size = MSGQ_MSG_WSIZE(apm_raw_data_msg_t) - MSGQ_MESSAGE_HEADER_WSIZE; return msgq_dequeue_message(&apm->msgq_send.msgq, &apm_msg->msgq_msg); } static int tegra210_adsp_send_msg(struct tegra210_adsp_app *app, apm_msg_t *apm_msg, uint32_t flags) { int ret = 0; unsigned long flag; if (flags & TEGRA210_ADSP_MSG_FLAG_NEED_ACK) { if (flags & TEGRA210_ADSP_MSG_FLAG_HOLD) { pr_err("%s: ACK requires FLAG_SEND, ignoring\n", __func__); flags &= ~TEGRA210_ADSP_MSG_FLAG_NEED_ACK; } else { apm_msg->msg.call_params.method |= NVFX_APM_METHOD_ACK_BIT; } } if (!IS_APM_IN(app->reg)) { uint32_t source; while (IS_ADSP_APP(app->reg) && !IS_APM_IN(app->reg)) { source = tegra210_adsp_get_source(app->adsp, app->reg); app = &app->adsp->apps[source]; } if (!IS_APM_IN(app->reg)) { pr_err("%s: No APM found, skip msg sending\n", __func__); return ret; } } spin_lock_irqsave(&app->apm_msg_queue_lock, flag); ret = msgq_queue_message(&app->apm->msgq_recv.msgq, &apm_msg->msgq_msg); spin_unlock_irqrestore(&app->apm_msg_queue_lock, flag); if (ret < 0) { /* Wakeup APM to consume messages and give it some time */ ret = nvadsp_mbox_send(&app->apm_mbox, apm_cmd_msg_ready, NVADSP_MBOX_SMSG, false, 0); if (ret) { pr_err("%s: Failed to send mailbox message id %d ret %d\n", __func__, app->apm->mbox_id, ret); } mdelay(20); /* Attempt queueing again */ spin_lock_irqsave(&app->apm_msg_queue_lock, flag); ret = msgq_queue_message(&app->apm->msgq_recv.msgq, &apm_msg->msgq_msg); spin_unlock_irqrestore(&app->apm_msg_queue_lock, flag); if (ret < 0) { pr_err("%s: Failed to queue message ret %d \ rd %d and wr %d pointer %p mbox_id %d\n", __func__, ret, app->apm->msgq_recv.msgq.read_index, app->apm->msgq_recv.msgq.write_index, &app->apm->msgq_recv.msgq, app->apm->mbox_id); return ret; } } if (flags & TEGRA210_ADSP_MSG_FLAG_HOLD) return 0; ret = nvadsp_mbox_send(&app->apm_mbox, apm_cmd_msg_ready, NVADSP_MBOX_SMSG, false, 0); if (ret) { pr_err("%s: Failed to send mailbox message id %d ret %d\n", __func__, app->apm->mbox_id, ret); } if (!(flags & TEGRA210_ADSP_MSG_FLAG_NEED_ACK)) return ret; ret = wait_for_completion_interruptible_timeout( app->msg_complete, msecs_to_jiffies(ADSP_RESPONSE_TIMEOUT)); if (WARN_ON(ret == 0)) pr_err("%s: ACK timed out %d rd %d wr %d mbox_id %d \ msg_q 0x%p\n", __func__, app->reg, app->apm->msgq_recv.msgq.read_index, app->apm->msgq_recv.msgq.write_index, app->apm->mbox_id, &app->apm->msgq_recv.msgq); return ret; } static int tegra210_adsp_send_raw_data_msg(struct tegra210_adsp_app *app, apm_raw_data_msg_t *apm_msg) { int ret = 0; struct tegra210_adsp_app *apm = app; unsigned long flag; /* Find parent APM to wait for ACK*/ if (!IS_APM_IN(apm->reg)) { uint32_t source; while (IS_ADSP_APP(apm->reg) && !IS_APM_IN(apm->reg)) { source = tegra210_adsp_get_source(apm->adsp, apm->reg); apm = &apm->adsp->apps[source]; } if (!IS_APM_IN(apm->reg)) { pr_err("%s: No APM found, skip ACK wait\n", __func__); return ret; } } apm_msg->msg.call_params.method |= NVFX_APM_METHOD_ACK_BIT; spin_lock_irqsave(&apm->apm_msg_queue_lock, flag); ret = msgq_queue_message(&app->apm->msgq_recv.msgq, &apm_msg->msgq_msg); spin_unlock_irqrestore(&apm->apm_msg_queue_lock, flag); if (ret < 0) { pr_err("%s failed: msgq full\n", __func__); return ret; } ret = nvadsp_mbox_send(&app->apm_mbox, apm_cmd_raw_data_ready, NVADSP_MBOX_SMSG, true, 100); if (ret) { pr_err("Failed to send mailbox message id %d ret %d\n", app->apm->mbox_id, ret); } ret = wait_for_completion_interruptible_timeout( apm->raw_msg_write_complete, msecs_to_jiffies(ADSP_RESPONSE_TIMEOUT)); if (WARN_ON(ret == 0)) pr_err("%s: ACK timed out %d\n", __func__, app->reg); return ret; } static int tegra210_adsp_send_remove_msg(struct tegra210_adsp_app *app, uint32_t flags) { apm_msg_t apm_msg; apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(apm_fx_remove_params_t); apm_msg.msg.call_params.size = sizeof(apm_fx_remove_params_t); apm_msg.msg.call_params.method = nvfx_apm_method_fx_remove_all; return tegra210_adsp_send_msg(app, &apm_msg, flags); } 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_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_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_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_msg, flags); } static int tegra210_adsp_eavbdma_params_msg(struct tegra210_adsp_app *app, nvfx_eavbdma_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_eavbdma_init_params_t); params->call_params.method = nvfx_eavbdma_method_init; memcpy(&apm_msg.msg.fx_set_param_params.params, params, sizeof(*params)); return tegra210_adsp_send_msg(app, &apm_msg, flags); } static void tegra_adsp_override_freq_worker(struct work_struct *work) { adsp_override_freq(INT_MAX); } 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; /* Spike ADSP freq to max when app transitions to active */ /* state; DFS will thereafter find appropriate rate */ if ((state == nvfx_state_active) && (app->override_freq_work != NULL)) schedule_work(app->override_freq_work); return tegra210_adsp_send_msg(app, &apm_msg, flags); } static int tegra210_adsp_send_flush_msg(struct tegra210_adsp_app *app, uint32_t flags) { apm_msg_t apm_msg; apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(nvfx_flush_params_t); apm_msg.msg.call_params.size = sizeof(nvfx_flush_params_t); apm_msg.msg.call_params.method = nvfx_method_flush; return tegra210_adsp_send_msg(app, &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_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_method_set_eos; return tegra210_adsp_send_msg(app, &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_msg, flags); } static int tegra210_adsp_send_data_request_msg(struct tegra210_adsp_app *app, uint32_t size, uint32_t flags) { apm_msg_t apm_msg; apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(apm_fx_read_request_params_t); apm_msg.msg.call_params.size = sizeof(apm_fx_read_request_params_t); apm_msg.msg.call_params.method = nvfx_apm_method_read_data; apm_msg.msg.fx_read_request_params.plugin.pvoid = app->plugin->plugin.pvoid; apm_msg.msg.fx_read_request_params.req_size = size; return tegra210_adsp_send_msg(app, &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; if (!app->desc->handle) { return -ENODEV; } app->info = nvadsp_app_init(app->desc->handle, NULL); if (IS_ERR_OR_NULL(app->info)) { dev_err(adsp->dev, "Failed to init app %s(%s).", app->desc->name, app->desc->fw_name); return -ENODEV; } dev_info(adsp->dev, "apm init app %s done\n", app->desc->name); spin_lock_init(&app->lock); spin_lock_init(&app->apm_msg_queue_lock); spin_lock_init(&app->fe_playback_lock); app->adsp = adsp; app->msg_handler = tegra210_adsp_app_default_msg_handler; app->override_freq_work = &adsp->override_freq_work; 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); app->info->stack_size = apm_stack_size[app->reg - APM_IN_START]; ret = nvadsp_mbox_open(&app->apm_mbox, &app->apm->mbox_id, app->desc->name, tegra210_adsp_msg_handler, app); if (ret < 0) { dev_err(adsp->dev, "Failed to open mailbox %s(%s).", app->desc->name, app->desc->fw_name); goto err_app_exit; } app->msg_complete = devm_kzalloc(adsp->dev, sizeof(struct completion), GFP_KERNEL); if (!app->msg_complete) { dev_err(adsp->dev, "Failed to allocate completion struct."); return -ENOMEM; } app->raw_msg_read_complete = devm_kzalloc(adsp->dev, sizeof(struct completion), GFP_KERNEL); if (!app->raw_msg_read_complete) { dev_err(adsp->dev, "Failed to allocate read completion struct."); return -ENOMEM; } app->raw_msg_write_complete = devm_kzalloc(adsp->dev, sizeof(struct completion), GFP_KERNEL); if (!app->raw_msg_write_complete) { dev_err(adsp->dev, "Failed to allocate read completion struct."); return -ENOMEM; } init_completion(app->msg_complete); init_completion(app->raw_msg_read_complete); init_completion(app->raw_msg_write_complete); ret = nvadsp_app_start(app->info); if (ret < 0) { dev_err(adsp->dev, "Failed to start adsp app"); goto err_mbox_close; } dev_info(adsp->dev, "apm start app %d msg_q 0x%p\n" ,app->apm->mbox_id, &app->apm->msgq_recv.msgq); /* Copy APM IN app data to APM OUT app */ apm_out->info = app->info; apm_out->plugin = app->plugin; apm_out->apm = app->apm; apm_out->adsp = app->adsp; apm_out->apm_mbox = app->apm_mbox; apm_out->msg_complete = app->msg_complete; apm_out->raw_msg_read_complete = app->raw_msg_read_complete; apm_out->raw_msg_write_complete = app->raw_msg_write_complete; } else if (IS_ADMA(app->reg)) { app->adma_chan = find_first_zero_bit(adsp->adma_usage, adsp->adma_ch_cnt); if (app->adma_chan >= adsp->adma_ch_cnt) { dev_err(adsp->dev, "All ADMA channels are busy"); return -EBUSY; } __set_bit(app->adma_chan, adsp->adma_usage); app->adma_chan += adsp->adma_ch_start; } return 0; err_mbox_close: nvadsp_mbox_close(&app->apm_mbox); err_app_exit: app->info = NULL; return 0; } static void tegra210_adsp_app_deinit(struct tegra210_adsp *adsp, struct tegra210_adsp_app *app) { /* TODO: * Current usecases use static paths so app_deinit functionality * is not needed and adds overhead of freeing and allocating apps * everytime. Add app deinit functionality properly if needed in * future. */ return; } /* API to connect two APMs */ static int tegra210_adsp_connect_apm(struct tegra210_adsp *adsp, struct tegra210_adsp_app *app) { uint32_t source = tegra210_adsp_get_source(adsp, app->reg); struct tegra210_adsp_app *src = &adsp->apps[source]; int ret = 0; /* 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_SEND); if (ret < 0) { dev_err(adsp->dev, "Connect msg failed. err %d.", ret); return ret; } return 1; } /* Recursive function to connect plugins under a APM Returns BE/FE on the pcm path */ static int tegra210_adsp_connect_plugin(struct tegra210_adsp *adsp, struct tegra210_adsp_app *app, uint32_t *apm_in_src) { 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, apm_in_src); if (ret < 0) return ret; } else { source = tegra210_adsp_get_source(adsp, src->reg); if (IS_APM_OUT(source)) { /* connect plugins inside next APM */ ret = tegra210_adsp_connect_plugin(adsp, &adsp->apps[source], apm_in_src); if (ret < 0) return ret; /* connect APM_IN to APM_OUT */ ret = tegra210_adsp_connect_apm(adsp, src); if (ret < 0) return ret; } else { /* return if APM_IN is not connected to valid inputs */ if (!IS_ADSP_FE(source) && !IS_ADSP_ADMAIF(source)) return -ENODEV; if (apm_in_src) *apm_in_src = source; } } app->apm = src->apm; app->apm_mbox = src->apm_mbox; /* 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_SEND | TEGRA210_ADSP_MSG_FLAG_NEED_ACK); 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; } /* Manages FE/BE plugins and deletes if fe_apm is specified */ static void tegra210_adsp_manage_plugin(struct tegra210_adsp *adsp, uint32_t end_reg, uint32_t apm_out, struct tegra210_adsp_app *fe_apm) { uint32_t j, fe_reg, be_reg; if (IS_ADSP_FE(end_reg)) { /* manage playback path */ fe_reg = end_reg; be_reg = 0; for (j = ADSP_ADMAIF_START; j <= ADSP_ADMAIF_END; j++) { if (tegra210_adsp_get_source(adsp, j) == apm_out) { be_reg = j; break; } } if (be_reg && fe_reg) { if (fe_apm) { dev_vdbg(adsp->dev, "Remove playback FE %d -- BE %d pair", fe_reg, be_reg); tegra210_adsp_send_remove_msg(fe_apm, TEGRA210_ADSP_MSG_FLAG_SEND); } else { dev_vdbg(adsp->dev, "Found playback FE %d -- BE %d pair", fe_reg, be_reg); } } } else if (IS_ADSP_ADMAIF(end_reg)) { /* manage record path */ fe_reg = 0; be_reg = end_reg; for (j = ADSP_FE_START; j <= ADSP_FE_END; j++) { if (tegra210_adsp_get_source(adsp, j) == apm_out) { fe_reg = j; break; } } if (be_reg && fe_reg) { if (fe_apm) { dev_vdbg(adsp->dev, "Remove record FE %d -- BE %d pair", fe_reg, be_reg); tegra210_adsp_send_remove_msg(fe_apm, TEGRA210_ADSP_MSG_FLAG_SEND); } else { dev_vdbg(adsp->dev, "Found playback FE %d -- BE %d pair", fe_reg, be_reg); } } } } /* Iterate over all APMs and establish pending connections */ static int tegra210_adsp_update_connection(struct tegra210_adsp *adsp) { int i, ret; uint32_t end_reg; for (i = APM_OUT_START; i <= APM_OUT_END; i++) { ret = tegra210_adsp_connect_plugin(adsp, &adsp->apps[i], &end_reg); if (ret >= 0) { /* Record FE/BE pair for every successful connection */ tegra210_adsp_manage_plugin(adsp, end_reg, i, NULL); } } return 0; } /* Remove the plugin connections inside associated APM */ static int tegra210_adsp_remove_connection(struct tegra210_adsp *adsp, struct tegra210_adsp_app *plugin) { struct tegra210_adsp_app *app; uint32_t i, source, apm_out = TEGRA210_ADSP_NONE; if (!IS_ADSP_APP(plugin->reg)) return 0; for (i = APM_OUT_START; i <= APM_OUT_END; i++) { app = &adsp->apps[i]; /* if the path is already broken, do not continue */ if (!app->connect) continue; while (IS_ADSP_APP(app->reg)) { if (app->reg == plugin->reg) { apm_out = i; break; } source = tegra210_adsp_get_source(adsp, app->reg); app = &adsp->apps[source]; } if (apm_out != TEGRA210_ADSP_NONE) break; } /* if plugin is not part of any APM, return here */ if (apm_out == TEGRA210_ADSP_NONE) return 0; /* disconnect the plugins inside APM */ app = &adsp->apps[apm_out]; while (!IS_APM_IN(app->reg)) { source = tegra210_adsp_get_source(adsp, app->reg); if (!IS_ADSP_APP(source)) break; app->connect = 0; app = &adsp->apps[source]; } /* delete the plugins inside APM */ if (IS_APM_IN(app->reg)) { /* clear the FE/BE list */ tegra210_adsp_manage_plugin(adsp, tegra210_adsp_get_source(adsp, app->reg), apm_out, app); } return 0; } /* ADSP socket message handlers */ static void tegra210_adsp_nl_recv_msg(struct sk_buff *skb) { struct nlmsghdr *nlh; nlh = (struct nlmsghdr *)skb->data; pr_info("Established client: %d\n", nlh->nlmsg_pid); return; } static void tegra210_adsp_nl_send_msg(struct tegra210_adsp *adsp, apm_fx_error_event_params_t *apm_err_msg) { struct sk_buff *skb; struct nlmsghdr *nlh; int res; struct adsp_event_nlmsg *msg; if (adsp->nl_sk == NULL) { pr_err("socket not created\n"); return; } skb = nlmsg_new(sizeof(struct adsp_event_nlmsg), 0); if (!skb) { pr_err("Failed to allocate new skb\n"); return; } nlh = nlmsg_put(skb, 0, 0, NLMSG_DONE, sizeof(struct adsp_event_nlmsg), 0); NETLINK_CB(skb).dst_group = NETLINK_ADSP_EVENT_GROUP; /* to mcast group 1<<0 */ msg = (struct adsp_event_nlmsg *)nlmsg_data(nlh); msg->err = apm_err_msg->err; memcpy(msg->data, apm_err_msg->data, (sizeof(uint32_t) * NVFX_MAX_CALL_PARAMS_WSIZE)); res = nlmsg_multicast(adsp->nl_sk, skb, 0, NETLINK_ADSP_EVENT_GROUP, 0); if (res < 0) pr_err("Error while sending back to user: %d\n", res); return; } /* ADSP mailbox message handler */ static status_t tegra210_adsp_msg_handler(uint32_t msg, void *data) { struct tegra210_adsp_app *app = data; unsigned long flags; apm_msg_t apm_msg; int ret = 0; spin_lock_irqsave(&app->lock, flags); 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) ret = app->msg_handler(app, &apm_msg); } break; case apm_cmd_raw_data_ready: { apm_raw_data_msg_t *raw_msg = kzalloc(sizeof(apm_raw_data_msg_t), GFP_ATOMIC); if (!raw_msg) { ret = -ENOMEM; break; } ret = tegra210_adsp_get_raw_data_msg(app->apm, raw_msg); if (ret < 0) { pr_err("Dequeue failed raw %d.", ret); kfree(raw_msg); break; } memcpy(app->read_data.data, raw_msg->msg.fx_raw_data_params.data, sizeof(app->read_data.data)); kfree(raw_msg); complete(app->raw_msg_read_complete); } break; default: pr_err("Unsupported mailbox msg %d.", msg); } spin_unlock_irqrestore(&app->lock, flags); return ret; } static int tegra210_adsp_app_default_msg_handler(struct tegra210_adsp_app *app, apm_msg_t *apm_msg) { switch (apm_msg->msg.call_params.method) { case nvfx_apm_method_ack: complete(app->msg_complete); break; case nvfx_apm_method_raw_ack: complete(app->raw_msg_write_complete); break; case nvfx_apm_method_fx_error_event: tegra210_adsp_nl_send_msg(app->adsp, &apm_msg->msg.fx_error_event_params); break; default: pr_err("Unsupported cmd %d.", apm_msg->msg.call_params.method); } 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, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(prtd->dev, "Failed to send write position."); return ret; } return ret; } 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; struct snd_pcm_runtime *runtime; switch (apm_msg->msg.call_params.method) { case nvfx_apm_method_set_position: if (!prtd || !prtd->substream) return 0; runtime = prtd->substream->runtime; snd_pcm_period_elapsed(prtd->substream); if ((IS_MMAP_ACCESS(runtime->access))) { if (prtd->prev_appl_ptr != runtime->control->appl_ptr) { prtd->prev_appl_ptr = runtime->control->appl_ptr; tegra210_adsp_pcm_ack(prtd->substream); } } break; case nvfx_method_set_eos: /* Nothing specific to be done here as DRAIN */ /* is implemented in native PCM driver */ break; case nvfx_apm_method_ack: complete(app->msg_complete); break; case nvfx_apm_method_fx_error_event: tegra210_adsp_nl_send_msg(app->adsp, &apm_msg->msg.fx_error_event_params); 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; if (!prtd) { return 0; } switch (apm_msg->msg.call_params.method) { case nvfx_apm_method_set_position: snd_compr_fragment_elapsed(prtd->cstream); break; case nvfx_method_set_eos: if (!prtd->is_draining) { dev_warn(prtd->dev, "EOS reached before drain"); break; } snd_compr_drain_notify(prtd->cstream); prtd->is_draining = 0; break; case nvfx_apm_method_ack: complete(app->msg_complete); break; case nvfx_apm_method_fx_error_event: tegra210_adsp_nl_send_msg(app->adsp, &apm_msg->msg.fx_error_event_params); break; default: dev_err(prtd->dev, "Unsupported cmd %d.", apm_msg->msg.call_params.method); } return 0; } static int tegra_adsp_get_connected_be(struct tegra210_adsp *adsp, uint32_t fe_reg, int stream) { int sink = fe_reg; int i; if (stream == SNDRV_PCM_STREAM_CAPTURE) while ((sink = tegra210_adsp_get_source(adsp, sink)) != 0 && !(IS_ADSP_ADMAIF(sink) || IS_NULL_SINK(sink))) continue; else { for (i = ADSP_ADMAIF_START; i <= ADSP_ADMAIF_END; i++) { sink = i; while ((sink = tegra210_adsp_get_source(adsp, sink)) != 0 && sink != fe_reg) continue; if (sink == fe_reg) break; } if (i > ADSP_ADMAIF_END) return 0; sink = i; } if (IS_ADSP_ADMAIF(sink) || IS_NULL_SINK(sink)) return sink; return 0; } static int tegra_adsp_get_connected_fe(struct tegra210_adsp *adsp, uint32_t be_reg, int stream) { int src = be_reg; int i; if (stream == SNDRV_PCM_STREAM_PLAYBACK) while ((src = tegra210_adsp_get_source(adsp, src)) != 0 && !(IS_ADSP_FE(src) || IS_ADSP_ADMAIF(src))) continue; else { for (i = ADSP_FE_START; i <= ADSP_FE_END; i++) { src = i; while ((src = tegra210_adsp_get_source(adsp, src)) != 0 && src != be_reg) continue; if (src == be_reg) break; } if (i > ADSP_FE_END) return 0; src = i; } if (IS_ADSP_FE(src)) return src; 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 ret; int i; dev_vdbg(adsp->dev, "%s : DAI ID %d", __func__, rtd->codec_dai->id); if (!adsp->init_done || adsp->is_shutdown) return -ENODEV; if (!tegra_adsp_get_connected_be(adsp, fe_reg, cstream->direction)) { dev_err(adsp->dev, "Broken Path%d - FE not linked to BE", fe_reg); return -EPIPE; } 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; ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync error 0x%x\n", __func__, ret); } return ret; } static int tegra210_adsp_compr_free(struct snd_compr_stream *cstream) { struct tegra210_adsp_compr_rtd *prtd = cstream->runtime->private_data; unsigned long flags; if (!prtd) return -ENODEV; tegra210_adsp_send_reset_msg(prtd->fe_apm, TEGRA210_ADSP_MSG_FLAG_SEND | TEGRA210_ADSP_MSG_FLAG_NEED_ACK); pm_runtime_put(prtd->dev); tegra210_adsp_deallocate_dma_buffer(&prtd->buf); spin_lock_irqsave(&prtd->fe_apm->lock, flags); /* Reset msg handler to disable msg processing */ prtd->fe_apm->msg_handler = tegra210_adsp_app_default_msg_handler; spin_unlock_irqrestore(&prtd->fe_apm->lock, flags); cstream->runtime->private_data = NULL; 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_SEND); 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_SEND); 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, TEGRA210_ADSP_MSG_FLAG_SEND); 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, TEGRA210_ADSP_MSG_FLAG_SEND); 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, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(prtd->dev, "Failed to set state stop"); return ret; } ret = tegra210_adsp_send_flush_msg(prtd->fe_apm, TEGRA210_ADSP_MSG_FLAG_SEND); 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, TEGRA210_ADSP_MSG_FLAG_SEND); 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, TEGRA210_ADSP_MSG_FLAG_SEND); 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, TEGRA210_ADSP_MSG_FLAG_SEND); 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; uint32_t frames_played = (shared->output[0].bytes >> 1) / prtd->codec.ch_in; tstamp->byte_offset = shared->input[0].bytes % cstream->runtime->buffer_size; tstamp->copied_total = shared->input[0].bytes; tstamp->pcm_frames = frames_played; /* TODO : calculate IO frames correctly */ tstamp->pcm_io_frames = frames_played; tstamp->sampling_rate = snd_pcm_rate_bit_to_rate(prtd->codec.sample_rate); 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__); if (adsp->is_shutdown) return -ENODEV; if (!tegra_adsp_get_connected_be(adsp, fe_reg, substream->stream)) { dev_err(adsp->dev, "Broken Path%d - FE not linked to BE", fe_reg); return -EPIPE; } 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; } if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && tegra_adsp_get_connected_be(adsp, fe_reg, substream->stream) == 0) { 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_SIZE, 0x4); if (ret) { dev_err(adsp->dev, "failed to set period_size constraint %d\n", ret); return ret; } /* Ensure buffer size is multiple of 4 */ ret = snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 0x4); if (ret) { dev_err(adsp->dev, "failed to set buffer_size 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; unsigned long flags; dev_vdbg(prtd->dev, "%s", __func__); tegra_isomgr_adma_setbw(substream, false); if (prtd) { tegra210_adsp_send_reset_msg(prtd->fe_apm, TEGRA210_ADSP_MSG_FLAG_SEND | TEGRA210_ADSP_MSG_FLAG_NEED_ACK); spin_lock_irqsave(&prtd->fe_apm->lock, flags); /* Reset msg handler to disable msg processing */ prtd->fe_apm->msg_handler = tegra210_adsp_app_default_msg_handler; prtd->fe_apm->private_data = NULL; spin_unlock_irqrestore(&prtd->fe_apm->lock, flags); prtd->fe_apm->fe = 1; substream->runtime->private_data = NULL; devm_kfree(prtd->dev, prtd); } return 0; } static int tegra210_adsp_pcm_prepare(struct snd_pcm_substream *substream) { struct tegra210_adsp_pcm_rtd *prtd = substream->runtime->private_data; struct tegra210_adsp_app *apm = prtd->fe_apm; unsigned long flags; spin_lock_irqsave(&apm->fe_playback_lock, flags); apm->fe_playback_triggered = 0; spin_unlock_irqrestore(&apm->fe_playback_lock, flags); tegra_isomgr_adma_setbw(substream, true); 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_SEND); if (ret < 0) return ret; ret = tegra210_adsp_send_period_size_msg(prtd->fe_apm, params_buffer_bytes(params)/params_periods(params), TEGRA210_ADSP_MSG_FLAG_SEND); 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 tegra_ivc_start_playback( struct tegra210_adsp *adsp, uint32_t ivc_msg_admaif_id, bool ack_required) { int err = 0; struct nvaudio_ivc_msg msg; memset(&msg, 0, sizeof(struct nvaudio_ivc_msg)); msg.cmd = NVAUDIO_START_PLAYBACK; msg.params.dmaif_info.id = ivc_msg_admaif_id; msg.ack_required = ack_required; if (ack_required) err = nvaudio_ivc_send_receive(adsp->hivc_client, &msg, sizeof(struct nvaudio_ivc_msg)); else err = nvaudio_ivc_send_retry(adsp->hivc_client, &msg, sizeof(struct nvaudio_ivc_msg)); return err; } static int tegra_ivc_start_capture( struct tegra210_adsp *adsp, uint32_t ivc_msg_admaif_id, bool ack_required) { int err = 0; struct nvaudio_ivc_msg msg; memset(&msg, 0, sizeof(struct nvaudio_ivc_msg)); msg.cmd = NVAUDIO_START_CAPTURE; msg.ack_required = ack_required; msg.params.dmaif_info.id = ivc_msg_admaif_id; if (ack_required) err = nvaudio_ivc_send_receive(adsp->hivc_client, &msg, sizeof(struct nvaudio_ivc_msg)); else err = nvaudio_ivc_send_retry(adsp->hivc_client, &msg, sizeof(struct nvaudio_ivc_msg)); return err; } static int tegra_ivc_stop_playback( struct tegra210_adsp *adsp, uint32_t ivc_msg_admaif_id, bool ack_required) { int err = 0; struct nvaudio_ivc_msg msg; memset(&msg, 0, sizeof(struct nvaudio_ivc_msg)); msg.cmd = NVAUDIO_STOP_PLAYBACK; msg.params.dmaif_info.id = ivc_msg_admaif_id; msg.ack_required = ack_required; if (ack_required) err = nvaudio_ivc_send_receive(adsp->hivc_client, &msg, sizeof(struct nvaudio_ivc_msg)); else err = nvaudio_ivc_send_retry(adsp->hivc_client, &msg, sizeof(struct nvaudio_ivc_msg)); return err; } static int tegra_ivc_stop_capture( struct tegra210_adsp *adsp, uint32_t ivc_msg_admaif_id, bool ack_required) { int err = 0; struct nvaudio_ivc_msg msg; memset(&msg, 0, sizeof(struct nvaudio_ivc_msg)); msg.cmd = NVAUDIO_STOP_CAPTURE; msg.params.dmaif_info.id = ivc_msg_admaif_id; msg.ack_required = ack_required; if (ack_required) err = nvaudio_ivc_send_receive(adsp->hivc_client, &msg, sizeof(struct nvaudio_ivc_msg)); else err = nvaudio_ivc_send_retry(adsp->hivc_client, &msg, sizeof(struct nvaudio_ivc_msg)); return err; } /** * Function that checks for IO to IO path during widget event * Used to toggle playback/capture through IVC to Audio Server */ static int tegra210_adsp_send_hv_state_msg( struct tegra210_adsp *adsp, struct tegra210_adsp_app *app, int32_t state, int32_t is_playback) { uint32_t src, i; int32_t ret = 0; uint32_t be_to_be_flag = 0; int32_t apm_in_id = -1, apm_out_id = -1; int32_t playback_admaif_id = -1, capture_admaif_id = -1; struct device *dev = adsp->dev; struct device_node *node = dev->of_node; /* Only applicable for HV configurations */ if (!of_device_is_compatible(node, "nvidia,tegra210-adsp-audio-hv")) return 0; apm_in_id = app->reg - APM_IN_START; /* * For IO to IO path, APM-IN input should be ADSP-ADMAIF * and APM-OUT output should be connected to ADSP-ADMAIF */ if (state == nvfx_state_active) { /* Check source of APM-IN. If not ADSP-ADMAIF, return */ src = tegra210_adsp_get_source(adsp, app->reg); if (!IS_ADSP_ADMAIF(src)) { adsp->apm_to_admaif_map[apm_in_id][SNDRV_PCM_STREAM_PLAYBACK] = -1; adsp->apm_to_admaif_map[apm_in_id][SNDRV_PCM_STREAM_CAPTURE] = -1; return 0; } capture_admaif_id = src - ADSP_ADMAIF_START; /* Check if any ADSP-ADMAIF is connected to same APM output */ for (i = ADSP_ADMAIF_START; i <= ADSP_ADMAIF_END; i++) { app = &adsp->apps[i]; src = tegra210_adsp_get_source(adsp, app->reg); if (!IS_APM_OUT(src)) continue; apm_out_id = src - APM_OUT_START; if (apm_out_id == apm_in_id) { be_to_be_flag = 1; playback_admaif_id = app->reg - ADSP_ADMAIF_START; break; } } if (be_to_be_flag) { /* if IO to IO path exists, map APM to ADMAIFs */ adsp->apm_to_admaif_map[apm_in_id][SNDRV_PCM_STREAM_PLAYBACK] = playback_admaif_id; adsp->apm_to_admaif_map[apm_in_id][SNDRV_PCM_STREAM_CAPTURE] = capture_admaif_id; if (is_playback == SNDRV_PCM_STREAM_PLAYBACK) { ret = tegra_ivc_start_playback(adsp, playback_admaif_id, false); if (ret < 0) dev_err(adsp->dev, "%s: start playback failed\n", __func__); } else { ret = tegra_ivc_start_capture(adsp, capture_admaif_id, false); if (ret < 0) { dev_err(adsp->dev, "%s: start capture failed\n", __func__); ret = tegra_ivc_stop_playback(adsp, playback_admaif_id, false); if (ret < 0) dev_err(adsp->dev, "%s: stop capture failed\n", __func__); } } } else { /* No IO to IO path identified. No hv state msg to be sent */ adsp->apm_to_admaif_map[apm_in_id][SNDRV_PCM_STREAM_PLAYBACK] = -1; adsp->apm_to_admaif_map[apm_in_id][SNDRV_PCM_STREAM_CAPTURE] = -1; } } else if (state == nvfx_state_inactive) { playback_admaif_id = adsp->apm_to_admaif_map[apm_in_id][SNDRV_PCM_STREAM_PLAYBACK]; capture_admaif_id = adsp->apm_to_admaif_map[apm_in_id][SNDRV_PCM_STREAM_CAPTURE]; if ((playback_admaif_id != -1) && (capture_admaif_id != -1)) { if (is_playback == SNDRV_PCM_STREAM_PLAYBACK) { ret = tegra_ivc_stop_playback(adsp, playback_admaif_id, false); if (ret < 0) dev_err(adsp->dev, "%s: stop playback failed\n", __func__); } else { ret = tegra_ivc_stop_capture(adsp, capture_admaif_id, true); if (ret < 0) dev_err(adsp->dev, "%s: stop capture failed\n", __func__); } } if (is_playback == SNDRV_PCM_STREAM_PLAYBACK) { adsp->apm_to_admaif_map[apm_in_id][SNDRV_PCM_STREAM_PLAYBACK] = -1; adsp->apm_to_admaif_map[apm_in_id][SNDRV_PCM_STREAM_CAPTURE] = -1; } } return ret; } static uint32_t tegra210_adsp_hv_pcm_trigger( struct tegra210_adsp *adsp, uint32_t apm_out_in, int stream, int cmd) { int ret = 0; int32_t ivc_msg_admaif_id = 0; uint32_t source; source = tegra210_adsp_get_source(adsp, apm_out_in); /* return if this is playback request on NULL-SINK */ if (IS_NULL_SINK(tegra_adsp_get_connected_be(adsp, source, SNDRV_PCM_STREAM_PLAYBACK))) return 0; ivc_msg_admaif_id = ADSP_BACKEND_TO_ADMAIF( tegra_adsp_get_connected_be(adsp, source, stream)); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (stream == SNDRV_PCM_STREAM_PLAYBACK) { ret = tegra_ivc_start_playback(adsp, ivc_msg_admaif_id, true); } else { ret = tegra_ivc_start_capture(adsp, ivc_msg_admaif_id, true); } if (ret < 0) { pr_err("%s: error during start_trigger\n", __func__); return ret; } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: if (stream == SNDRV_PCM_STREAM_PLAYBACK) { ret = tegra_ivc_stop_playback(adsp, ivc_msg_admaif_id, true); } else { ret = tegra_ivc_stop_capture(adsp, ivc_msg_admaif_id, true); } if (ret < 0) { pr_err("%s: error during stop_trigger\n", __func__); return ret; } break; case SNDRV_PCM_TRIGGER_DRAIN: /* do nothing */ break; default: pr_err("Unsupported state."); return -EINVAL; } return ret; } static int tegra210_adsp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; struct tegra210_adsp_pcm_rtd *prtd = substream->runtime->private_data; struct tegra210_adsp *adsp = prtd->fe_apm->adsp; struct device *dev = adsp->dev; struct device_node *node = dev->of_node; struct tegra210_adsp_app *apm = prtd->fe_apm; unsigned long flags; int ret = 0; dev_vdbg(prtd->dev, "%s : state %d", __func__, cmd); if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && tegra_adsp_get_connected_be(adsp, soc_runtime->codec_dai->id, substream->stream) == 0) { runtime->status->state = SNDRV_PCM_STATE_DISCONNECTED; return -EBADF; } spin_lock_irqsave(&apm->fe_playback_lock, flags); if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && (cmd == SNDRV_PCM_TRIGGER_STOP)) { if (apm->fe_playback_triggered == 1) apm->fe_playback_triggered = 0; else { /*unlock and return */ spin_unlock_irqrestore(&apm->fe_playback_lock, flags); return 0; } } if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && (cmd == SNDRV_PCM_TRIGGER_START)) { if (apm->fe_playback_triggered == 0) apm->fe_playback_triggered = 1; else { spin_unlock_irqrestore(&apm->fe_playback_lock, flags); return 0; } } spin_unlock_irqrestore(&apm->fe_playback_lock, flags); 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, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(prtd->dev, "Failed to set state"); return ret; } if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && (IS_MMAP_ACCESS(runtime->access))) { prtd->prev_appl_ptr = runtime->control->appl_ptr; tegra210_adsp_pcm_ack(substream); } break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: ret = tegra210_adsp_send_state_msg(prtd->fe_apm, nvfx_state_inactive, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(prtd->dev, "Failed to set state"); return ret; } break; case SNDRV_PCM_TRIGGER_STOP: ret = tegra210_adsp_send_state_msg(prtd->fe_apm, nvfx_state_inactive, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(prtd->dev, "Failed to set state"); return ret; } ret = tegra210_adsp_send_flush_msg(prtd->fe_apm, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(prtd->dev, "Failed to reset"); return ret; } break; case SNDRV_PCM_TRIGGER_DRAIN: /* EOS message is sent so that ADSP sends */ /* notification for last consumed buffer */ ret = tegra210_adsp_send_eos_msg(prtd->fe_apm, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(prtd->dev, "Failed to set state drain"); return ret; } break; default: dev_err(prtd->dev, "Unsupported state."); return -EINVAL; } if (of_device_is_compatible(node, "nvidia,tegra210-adsp-audio-hv")) { ret = tegra210_adsp_hv_pcm_trigger(adsp, prtd->fe_apm->reg, /* apm_out_in */ substream->stream, cmd); if (ret < 0) { dev_err(prtd->dev, "error on ivc_send"); return ret; } } return 0; } 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, .prepare = tegra210_adsp_pcm_prepare, .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_CAPTURE].substream) { int stream = SNDRV_PCM_STREAM_CAPTURE; tegra210_adsp_deallocate_dma_buffer( &pcm->streams[stream].substream->dma_buffer); } } static void tegra_adsp_set_admaif_id( struct tegra210_adsp *adsp, uint32_t admaif_id, uint32_t be_reg, int s_stream) { int i, j, stream; uint32_t src; struct tegra210_adsp_app *app = NULL; /* ADSP Playback (ADSP-ADMAIF Codec Capture) */ if (s_stream == SNDRV_PCM_STREAM_CAPTURE) { app = &adsp->apps[be_reg]; src = app->reg; while (src != TEGRA210_ADSP_NONE) { /* Backtrace path till data source is found */ src = tegra210_adsp_get_source(adsp, src); /* Check if data source in path is ADSP-FE (Mem to IO) */ if (IS_ADSP_FE(src)) { i = adsp->apps[src].reg; stream = SNDRV_PCM_STREAM_PLAYBACK; adsp->fe_to_admaif_map[i-1][stream] = admaif_id; dev_vdbg(adsp->dev, "%s : capture fe %d admaif %d\n", __func__, i, admaif_id); return; } /* Check if data source in path is ADSP-ADMAIF (IO to IO) */ if (IS_ADSP_ADMAIF(src)) { dev_vdbg(adsp->dev, "%s : start playback on adsp admaif %d\n", __func__, admaif_id); /* Clear older FE to ADMAIF mappings for given ADMAIF */ stream = SNDRV_PCM_STREAM_PLAYBACK; for (j = ADSP_FE_START; j <= ADSP_FE_END; j++) { if (adsp->fe_to_admaif_map[j-1][stream] == admaif_id) adsp->fe_to_admaif_map[j-1][stream] = -1; } return; } } dev_err(adsp->dev, "%s: no data source for ADSP-ADMAIF %d\n", __func__, admaif_id); } else { /* ADSP Capture (ADSP-ADMAIF Codec Playback) */ /* Check if ADSP-FE is sink for given ADSP ADMAIF (IO to Mem) */ for (i = ADSP_FE_START; i <= ADSP_FE_END; i++) { app = &adsp->apps[i]; src = app->reg; while (!IS_ADSP_ADMAIF(src) && src != 0) { src = tegra210_adsp_get_source( adsp, src); } if (src != be_reg) continue; stream = SNDRV_PCM_STREAM_CAPTURE; adsp->fe_to_admaif_map[i-1][stream] = admaif_id; dev_vdbg(adsp->dev, "%s : capture fe %d admaif %d\n", __func__, i, admaif_id); return; } /* Check if ADSP-ADMAIF is sink for given ADSP ADMAIF (IO to IO) */ for (i = ADSP_ADMAIF_START; i <= ADSP_ADMAIF_END; i++) { app = &adsp->apps[i]; src = app->reg; while (!IS_ADSP_ADMAIF(src) && src != 0) { src = tegra210_adsp_get_source( adsp, src); } if (src != be_reg) continue; /* Clear older FE to ADMAIF mappings for given ADMAIF */ stream = SNDRV_PCM_STREAM_CAPTURE; for (j = ADSP_FE_START; j <= ADSP_FE_END; j++) { if (adsp->fe_to_admaif_map[j-1][stream] == admaif_id) adsp->fe_to_admaif_map[j-1][stream] = -1; } dev_vdbg(adsp->dev, "%s : start capture on adsp admaif %d\n", __func__, admaif_id); return; } dev_err(adsp->dev, "%s: no data sink for ADSP-ADMAIF %d\n", __func__, admaif_id); } return; } static int tegra_adsp_admaif_ivc_set_cif(struct tegra210_adsp *adsp, struct snd_pcm_hw_params *params, uint32_t admaif_id, uint32_t be_reg, nvfx_adma_init_params_t *adma_params, int stream) { int ret = 0; uint32_t ivc_msg_admaif_id; struct tegra210_adsp_app *app; struct tegra210_virt_audio_cif cif_setting; struct tegra210_virt_audio_cif *cif_conf = NULL; struct tegra210_adsp_pcm_rtd *prtd = NULL; struct snd_pcm_runtime *runtime = NULL; struct nvaudio_ivc_msg msg; uint32_t value, channels = 2, format = SNDRV_PCM_FORMAT_S16_LE; uint32_t rate = 48000, apm_in_reg, source; uint32_t max_bytes = adsp_pcm_hardware.buffer_bytes_max; adsp->hivc_client = nvaudio_ivc_alloc_ctxt(adsp->dev); if (!adsp->hivc_client) { dev_err(adsp->dev, "Failed to allocate IVC context\n"); ret = -ENODEV; return ret; } if (params) { channels = params_channels(params); format = params_format(params); } /* overwrite channel, rate, format if we already have FE info */ if (stream == SNDRV_PCM_STREAM_CAPTURE) { ret = tegra_adsp_get_connected_fe(adsp, be_reg, SNDRV_PCM_STREAM_PLAYBACK); if (ret != TEGRA210_ADSP_NONE) { format = adsp->pcm_path [ret][SNDRV_PCM_STREAM_PLAYBACK].format; channels = adsp->pcm_path [ret][SNDRV_PCM_STREAM_PLAYBACK].channels; rate = adsp->pcm_path [ret][SNDRV_PCM_STREAM_PLAYBACK].rate; } 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; apm_in_reg = APM_IN_START + (source - APM_OUT_START); if (adsp->apps[apm_in_reg].msg_handler == tegra210_adsp_pcm_msg_handler) { prtd = adsp->apps[apm_in_reg].private_data; runtime = prtd->substream->runtime; max_bytes = frames_to_bytes(runtime, runtime->buffer_size); } } else { ret = tegra_adsp_get_connected_fe(adsp, be_reg, SNDRV_PCM_STREAM_CAPTURE); if (ret != TEGRA210_ADSP_NONE) { format = adsp->pcm_path [ret][SNDRV_PCM_STREAM_CAPTURE].format; channels = adsp->pcm_path [ret][SNDRV_PCM_STREAM_CAPTURE].channels; rate = adsp->pcm_path [ret][SNDRV_PCM_STREAM_CAPTURE].rate; } } cif_conf = &cif_setting; ivc_msg_admaif_id = admaif_id - 1; memset(cif_conf, 0, sizeof(struct tegra210_virt_audio_cif)); cif_conf->audio_channels = channels; cif_conf->client_channels = channels; switch (format) { case SNDRV_PCM_FORMAT_S8: cif_conf->client_bits = TEGRA210_AUDIOCIF_BITS_8; cif_conf->audio_bits = TEGRA210_AUDIOCIF_BITS_8; adma_params->burst_size = channels/4; adma_params->intr_dur = 1000 * max_bytes / (channels * rate); break; case SNDRV_PCM_FORMAT_S16_LE: cif_conf->client_bits = TEGRA210_AUDIOCIF_BITS_16; cif_conf->audio_bits = TEGRA210_AUDIOCIF_BITS_16; adma_params->burst_size = (channels * 2)/4; adma_params->intr_dur = 1000 * max_bytes / (channels * rate * 2); break; case SNDRV_PCM_FORMAT_S24_LE: cif_conf->client_bits = TEGRA210_AUDIOCIF_BITS_24; cif_conf->audio_bits = TEGRA210_AUDIOCIF_BITS_24; adma_params->burst_size = (channels * 3)/4; adma_params->intr_dur = 1000 * max_bytes / (channels * rate * 3); break; case SNDRV_PCM_FORMAT_S32_LE: cif_conf->client_bits = TEGRA210_AUDIOCIF_BITS_32; cif_conf->audio_bits = TEGRA210_AUDIOCIF_BITS_32; adma_params->burst_size = channels; adma_params->intr_dur = 1000 * max_bytes / (channels * rate * 4); break; default: dev_err(adsp->dev, "Wrong format!\n"); return -EINVAL; } cif_conf->direction = stream; value = (cif_conf->threshold << TEGRA210_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | ((cif_conf->audio_channels - 1) << TEGRA210_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | ((cif_conf->client_channels - 1) << TEGRA210_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | (cif_conf->audio_bits << TEGRA210_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) | (cif_conf->client_bits << TEGRA210_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) | (cif_conf->expand << TEGRA210_AUDIOCIF_CTRL_EXPAND_SHIFT) | (cif_conf->stereo_conv << TEGRA210_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) | (cif_conf->replicate << TEGRA210_AUDIOCIF_CTRL_REPLICATE_SHIFT) | (cif_conf->truncate << TEGRA210_AUDIOCIF_CTRL_TRUNCATE_SHIFT) | (cif_conf->mono_conv << TEGRA210_AUDIOCIF_CTRL_MONO_CONV_SHIFT); memset(&msg, 0, sizeof(struct nvaudio_ivc_msg)); msg.params.dmaif_info.id = ivc_msg_admaif_id; msg.params.dmaif_info.value = value; if (cif_conf->direction) msg.cmd = NVAUDIO_DMAIF_SET_TXCIF; else msg.cmd = NVAUDIO_DMAIF_SET_RXCIF; ret = nvaudio_ivc_send_retry(adsp->hivc_client, &msg, sizeof(struct nvaudio_ivc_msg)); if (ret < 0) pr_err("%s: error during ivc_send\n", __func__); return ret; } static int tegra210_adsp_admaif_hv_hw_params( struct tegra210_adsp *adsp, struct snd_pcm_hw_params *params, uint32_t admaif_id, uint32_t be_reg, nvfx_adma_init_params_t *adma_params, int stream) { int ret = 0; ret = tegra_adsp_admaif_ivc_set_cif(adsp, params, admaif_id, be_reg, adma_params, stream); if (ret < 0) { pr_err("%s: error during adsp_admaif_set_cif\n", __func__); return ret; } tegra_adsp_set_admaif_id(adsp, admaif_id, be_reg, stream); return ret; } static int tegra210_adsp_fe_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { return snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &tegra210_adsp_rate_constraints); } static int tegra210_adsp_fe_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); uint32_t fe_reg = dai->id; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { adsp->pcm_path[fe_reg][SNDRV_PCM_STREAM_PLAYBACK].rate = params_rate(params); adsp->pcm_path[fe_reg][SNDRV_PCM_STREAM_PLAYBACK].channels = params_channels(params); adsp->pcm_path[fe_reg][SNDRV_PCM_STREAM_PLAYBACK].format = params_format(params); } else { adsp->pcm_path[fe_reg][SNDRV_PCM_STREAM_CAPTURE].rate = params_rate(params); adsp->pcm_path[fe_reg][SNDRV_PCM_STREAM_CAPTURE].channels = params_channels(params); adsp->pcm_path[fe_reg][SNDRV_PCM_STREAM_CAPTURE].format = params_format(params); } return 0; } static int tegra210_adsp_switch_active_fe(struct tegra210_adsp *adsp, int32_t be_reg) { int ret, i; struct tegra210_adsp_switch *sch = &adsp->switches[0]; ret = tegra_adsp_get_connected_fe(adsp, be_reg, SNDRV_PCM_STREAM_PLAYBACK); if (ret != TEGRA210_ADSP_NONE) { for (i = 0; i < MAX_ADSP_SWITCHES; i++) { if (sch->active_fe == ret) return sch->admaif_id; sch = &adsp->switches[i+1]; } } return 0; } static int tegra210_adsp_null_sink_hw_params(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_platform *platform = snd_soc_dapm_to_platform(w->dapm); struct tegra210_adsp *adsp = snd_soc_platform_get_drvdata(platform); struct tegra210_adsp_app *app; nvfx_adma_init_params_t adma_params; struct tegra210_adsp_pcm_rtd *prtd = NULL; struct snd_pcm_runtime *runtime = NULL; uint32_t channels = 2, format = SNDRV_PCM_FORMAT_S16_LE, rate = 48000; int32_t source, be_reg = w->reg, apm_in_reg, ahub_chan = 0; int ret, num_params; apm_msg_t apm_msg; int32_t admaif_id; unsigned long flags; uint32_t max_bytes = adsp_pcm_hardware.buffer_bytes_max; if (!adsp->adsp_started) return 0; memset(&adma_params, 0, sizeof(adma_params)); adma_params.mode = ADMA_MODE_CONTINUOUS; adma_params.periods = 4; adma_params.adma_ch_page = adsp->adma_ch_page; 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; apm_in_reg = APM_IN_START + (source - APM_OUT_START); if (adsp->apps[apm_in_reg].msg_handler == tegra210_adsp_pcm_msg_handler) { prtd = adsp->apps[apm_in_reg].private_data; runtime = prtd->substream->runtime; max_bytes = frames_to_bytes(runtime, runtime->buffer_size); } source = tegra210_adsp_get_source(adsp, app->reg); app = &adsp->apps[source]; if (!IS_ADMA(app->reg)) return 0; ret = tegra_adsp_get_connected_fe(adsp, be_reg, SNDRV_PCM_STREAM_PLAYBACK); if (ret != TEGRA210_ADSP_NONE) { format = adsp->pcm_path [ret][SNDRV_PCM_STREAM_PLAYBACK].format; channels = adsp->pcm_path [ret][SNDRV_PCM_STREAM_PLAYBACK].channels; rate = adsp->pcm_path [ret][SNDRV_PCM_STREAM_PLAYBACK].rate; } switch (format) { case SNDRV_PCM_FORMAT_S8: adma_params.intr_dur = 1000 * max_bytes / (channels * rate * 1); break; case SNDRV_PCM_FORMAT_S16_LE: adma_params.intr_dur = 1000 * max_bytes / (channels * rate * 2); break; case SNDRV_PCM_FORMAT_S24_LE: adma_params.intr_dur = 1000 * max_bytes / (channels * rate * 3); break; case SNDRV_PCM_FORMAT_S32_LE: adma_params.intr_dur = 1000 * max_bytes / (channels * rate * 4); break; default: dev_err(adsp->dev, "Wrong format!\n"); return -EINVAL; } adma_params.adma_channel = app->adma_chan; adma_params.direction = ADMA_MEMORY_TO_AHUB; adma_params.event.pvoid = app->apm->output_event.pvoid; 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; num_params = 3; apm_msg.msg.fx_set_param_params.params[0] = (sizeof(nvfx_call_params_t) + num_params * sizeof(int32_t)); apm_msg.msg.fx_set_param_params.params[1] = nvfx_adma_set_null_sink_mode; ahub_chan = tegra210_adsp_switch_active_fe(adsp, be_reg); adma_params.ahub_channel = ahub_chan; if (event == SND_SOC_DAPM_POST_PMD) { spin_lock_irqsave(&adsp->switch_lock, flags); ret = tegra_adsp_get_connected_fe(adsp, be_reg, SNDRV_PCM_STREAM_PLAYBACK); admaif_id = adsp->fe_to_admaif_map[ret - 1] [SNDRV_PCM_STREAM_PLAYBACK]; if (admaif_id) { tegra_adsp_set_admaif_id(adsp, admaif_id, be_reg, SNDRV_PCM_STREAM_CAPTURE); adsp->is_fe_set[ret - 1] = false; spin_unlock_irqrestore(&adsp->switch_lock, flags); tegra_ivc_stop_playback(adsp, admaif_id - 1, true); return 0; } spin_unlock_irqrestore(&adsp->switch_lock, flags); /* set adma in playback mode */ apm_msg.msg.fx_set_param_params.params[2] = 0; } else /* set adma in drain mode */ apm_msg.msg.fx_set_param_params.params[2] = ahub_chan ? 0 : 1; ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync err 0x%x\n", __func__, ret); return ret; } if (ahub_chan) { tegra210_adsp_admaif_hv_hw_params(adsp, NULL, ahub_chan, be_reg, &adma_params, SNDRV_PCM_STREAM_CAPTURE); ret = tegra_adsp_get_connected_fe(adsp, be_reg, SNDRV_PCM_STREAM_PLAYBACK); if (!adsp->is_fe_set[ret - 1]) { tegra_ivc_start_playback(adsp, ahub_chan - 1, true); adsp->is_fe_set[ret - 1] = true; } } ret = tegra210_adsp_send_msg(app, &apm_msg, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_vdbg(adsp->dev, "apm null-sink msg failed.%d\n", ret); pm_runtime_put(adsp->dev); return ret; } if (event == SND_SOC_DAPM_POST_PMD) { pm_runtime_put(adsp->dev); return 0; } ret = tegra210_adsp_adma_params_msg(app, &adma_params, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_vdbg(adsp->dev, "ADMA param msg failed.%d\n", ret); pm_runtime_put(adsp->dev); return ret; } pm_runtime_put(adsp->dev); tegra_adsp_set_admaif_id(adsp, ahub_chan, be_reg, SNDRV_PCM_STREAM_CAPTURE); 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_pcm_rtd *prtd = NULL; struct snd_pcm_runtime *runtime = NULL; struct device *dev = adsp->dev; struct device_node *node = dev->of_node; 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, apm_in_reg; int i, ret; dev_vdbg(adsp->dev, "%s : stream %d admaif %d\n", __func__, substream->stream, admaif_id); if (!adsp->adsp_started) return -EINVAL; memset(&adma_params, 0, sizeof(adma_params)); if (of_device_is_compatible(node, "nvidia,tegra210-adsp-audio-hv")) { /*Start of sending IVC command for admaif cif settings*/ ret = tegra210_adsp_admaif_hv_hw_params(adsp, params, admaif_id, be_reg, &adma_params, substream->stream); if (ret < 0) { pr_err("%s: error during adsp_admaif_hv_hw_params\n", __func__); return ret; } /*End of sending IVC command for admaif cif setting*/ } adma_params.mode = ADMA_MODE_CONTINUOUS; adma_params.ahub_channel = admaif_id; adma_params.periods = 2; /* We need ping-pong buffers for ADMA */ adma_params.adma_ch_page = adsp->adma_ch_page; /* 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; apm_in_reg = APM_IN_START + (source - APM_OUT_START); /* not needed for IO-IO and compr */ if (adsp->apps[apm_in_reg].msg_handler == tegra210_adsp_pcm_msg_handler) { prtd = adsp->apps[apm_in_reg].private_data; runtime = prtd->substream->runtime; if ((IS_MMAP_ACCESS(runtime->access))) adma_params.periods = 4; } source = tegra210_adsp_get_source(adsp, app->reg); app = &adsp->apps[source]; if (!IS_ADMA(app->reg)) return 0; adma_params.adma_channel = app->adma_chan; 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, TEGRA210_ADSP_MSG_FLAG_SEND); 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]; /* not needed for IO-IO and compr */ if (app->msg_handler == tegra210_adsp_pcm_msg_handler) { prtd = adsp->apps[source].private_data; runtime = prtd->substream->runtime; } source = tegra210_adsp_get_source(adsp, app->reg); if (source != be_reg) continue; if (runtime && (IS_MMAP_ACCESS(runtime->access))) adma_params.periods = 4; app = &adsp->apps[i]; adma_params.adma_channel = app->adma_chan; 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, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(adsp->dev, "ADMA params msg failed"); return ret; } } } return 0; } /* ADSP-EAVB codec driver HW-params. Used for configuring EAVB ADMA */ static int tegra210_adsp_eavb_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_eavbdma_init_params_t eavbdma_params; int ret; if (!adsp->adsp_started) return -EINVAL; /* As a COCEC DAI, CAPTURE is transmit */ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { app = tegra210_adsp_get_plugin(adsp, EAVB_TX_PLUGIN); if (!app) return -EINVAL; eavbdma_params.direction = EAVB_TX_DMA; eavbdma_params.event.pvoid = app->apm->output_event.pvoid; } else { app = tegra210_adsp_get_plugin(adsp, EAVB_RX_PLUGIN); if (!app) return -EINVAL; eavbdma_params.direction = EAVB_RX_DMA; eavbdma_params.event.pvoid = app->apm->input_event.pvoid; } ret = tegra210_adsp_eavbdma_params_msg(app, &eavbdma_params, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(adsp->dev, "EAVB DMA params msg failed. %d.", ret); return ret; } return 0; } #ifdef CONFIG_PM static int tegra210_adsp_runtime_suspend(struct device *dev) { struct tegra210_adsp *adsp = dev_get_drvdata(dev); int ret = 0, i; dev_dbg(adsp->dev, "%s\n", __func__); mutex_lock(&adsp->mutex); if (!adsp->init_done || !adsp->adsp_started) goto exit; /* Check for msgq empty before suspend */ for (i = 0; i < TEGRA210_ADSP_VIRT_REG_MAX; i++) { struct tegra210_adsp_app *app = &adsp->apps[i]; if (app->plugin && IS_APM_IN(app->reg)) { msgq_t *msgq = &app->apm->msgq_recv.msgq; if (msgq->read_index == msgq->write_index) continue; pr_err("%s: app %d, msgq not empty rd %d wr %d\n", __func__, app->reg, msgq->read_index, msgq->write_index); } } ret = nvadsp_os_suspend(); if (ret) dev_err(adsp->dev, "Failed to suspend ADSP OS"); adsp->adsp_started = 0; if (!(tegra_platform_is_unit_fpga() || tegra_platform_is_fpga())) { if (!adsp->soc_data->is_soc_t210) clk_disable_unprepare(adsp->apb2ape_clk); clk_disable_unprepare(adsp->ahub_clk); clk_disable_unprepare(adsp->ape_clk); } exit: mutex_unlock(&adsp->mutex); return ret; } static int tegra210_adsp_runtime_resume(struct device *dev) { struct tegra210_adsp *adsp = dev_get_drvdata(dev); int ret = 0; dev_dbg(adsp->dev, "%s\n", __func__); mutex_lock(&adsp->mutex); if (!adsp->init_done || adsp->adsp_started) goto exit; if (!(tegra_platform_is_unit_fpga() || tegra_platform_is_fpga())) { ret = clk_prepare_enable(adsp->ahub_clk); if (ret < 0) { dev_err(dev, "ahub clk_enable failed: %d\n", ret); goto exit; } ret = clk_prepare_enable(adsp->ape_clk); if (ret < 0) { dev_err(dev, "ape clk_enable failed: %d\n", ret); goto exit; } if (!adsp->soc_data->is_soc_t210) { ret = clk_prepare_enable(adsp->apb2ape_clk); if (ret < 0) { dev_err(dev, "apb2ape clk_enable failed: %d\n" , ret); goto exit; } } } ret = nvadsp_os_start(); if (ret) { dev_err(adsp->dev, "Failed to start ADSP OS ret 0x%x", ret); adsp->adsp_started = 0; goto exit; } adsp->adsp_started = 1; exit: mutex_unlock(&adsp->mutex); return ret; } #endif /* ADSP platform driver read/write call-back */ static int tegra210_adsp_read(struct snd_soc_component *component, unsigned int reg, unsigned int *val) { struct snd_soc_platform *platform = snd_soc_component_to_platform(component); 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)); *val = tegra210_adsp_reg_read(adsp, reg); return 0; } static int tegra210_adsp_write(struct snd_soc_component *component, unsigned int reg, unsigned int val) { struct snd_soc_platform *platform = snd_soc_component_to_platform(component); 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_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); struct snd_soc_platform *platform = snd_soc_dapm_to_platform(dapm); 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_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); struct snd_soc_platform *platform = snd_soc_dapm_to_platform(dapm); 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; if (e->reg >= TEGRA210_ADSP_VIRT_REG_MAX) return -EINVAL; ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync error 0x%x\n", __func__, ret); return ret; } /* 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) { if (app->connect) { /* remove existing connections if any */ tegra210_adsp_remove_connection(adsp, app); } 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 %s(%s)", app->desc->name, app->desc->fw_name); goto err_put; } } } 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(dapm, kcontrol, val, e, NULL); err_put: pm_runtime_put(adsp->dev); return ret ? ret : 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_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_adsp *adsp = snd_soc_component_get_drvdata(cmpnt); 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_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_adsp *adsp = snd_soc_component_get_drvdata(cmpnt); 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; } #ifdef CONFIG_TEGRA_ADSP_LPTHREAD static int tegra210_adsp_lpthread_init_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_adsp *adsp = snd_soc_component_get_drvdata(cmpnt); if (!adsp->adsp_started) goto exit; ucontrol->value.enumerated.item[0] = adsp_usage_get(); exit: return 0; } static int tegra210_adsp_lpthread_init_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_adsp *adsp = snd_soc_component_get_drvdata(cmpnt); unsigned int init = ucontrol->value.enumerated.item[0]; if (!adsp->init_done) return -ENODEV; if (init) { pm_runtime_get_sync(adsp->dev); if (!adsp->adsp_started) goto exit; adsp_usage_set(init); } else { if (!adsp->adsp_started) goto exit; adsp_usage_set(init); pm_runtime_put_sync(adsp->dev); } exit: return 0; } #endif /* * ADSP FE DAPM Widget Event * Widget event allows realizing ADSP FE switch use cases * * Start Playback trigger calls handled by pcm_trigger() * * Path Connect when ADSP FE is in triggered state * (Sequence: Start trigger -> Path disconnect -> Path connect) * handled by fe_widget_event() in SND_SOC_DAPM_PRE_PMU event handling * * Stop Playback trigger calls and Path disconnect during playback * handled by fe_widget_event() in SND_SOC_DAPM_POST_PMD event handling */ static int tegra210_adsp_fe_widget_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_platform *platform = snd_soc_dapm_to_platform(w->dapm); struct tegra210_adsp *adsp = snd_soc_platform_get_drvdata(platform); struct tegra210_adsp_app *apm; struct device *dev = adsp->dev; struct device_node *node = dev->of_node; int i; int ret = 0; uint32_t source; struct snd_pcm_runtime *runtime; struct tegra210_adsp_pcm_rtd *prtd = NULL; unsigned long flags; /* Handle only HV environment ADSP FE events */ if (!of_device_is_compatible(node, "nvidia,tegra210-adsp-audio-hv")) return 0; if (!IS_ADSP_FE(w->reg)) return 0; /* Find out APM connected to ADSP-FE */ for (i = APM_IN_START; i <= APM_IN_END; i++) { apm = &adsp->apps[i]; source = tegra210_adsp_get_source(adsp, apm->reg); if (source == w->reg) break; } if (!IS_APM_IN(i)) { dev_err(adsp->dev, "%s ADSP-FE %d not connected to any APM-IN\n", __func__, w->reg); return -1; } ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync error 0x%x\n", __func__, ret); goto err_put; } /* * PRE_PMU: Send Start playback/capture IVC to Audio Server and * active state message to ADSP * * POST_PMD: Send Stop playback/capture IVC to Audio Server and * inactive state message to ADSP. Flush messages will be sent to * ADSP during pcm_close(). */ if (event == SND_SOC_DAPM_PRE_PMU) { dev_info(adsp->dev, "connect event on APM %d, ADSP-FE %d\n", (i + 1) - APM_IN_START, w->reg); /* * For trigger playback, pcm_trigger calls will handle ivc and adsp messages * * When ADSP FE is in triggered state, path disconnect and connect is done, * widget event will send IVC and ADSP messages */ spin_lock_irqsave(&apm->fe_playback_lock, flags); if (!apm->fe_playback_triggered) { ret = 0; spin_unlock_irqrestore(&apm->fe_playback_lock, flags); goto err_put; } apm->fe_playback_triggered = 1; spin_unlock_irqrestore(&apm->fe_playback_lock, flags); spin_lock_irqsave(&apm->lock, flags); prtd = apm->private_data; /* TODO can prtd be NULL here? */ runtime = prtd->substream->runtime; runtime->status->state = SNDRV_PCM_STATE_RUNNING; ret = tegra210_adsp_hv_pcm_trigger(adsp, apm->reg, /* apm_out_in */ SNDRV_PCM_STREAM_PLAYBACK, /* playback */ SNDRV_PCM_TRIGGER_START); /* PCM START */ if (ret < 0) { pr_err("%s: error during hv_pcm_trigger\n", __func__); goto err_put; } ret = tegra210_adsp_send_state_msg(apm, nvfx_state_active, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(adsp->dev, "Failed to set state active"); goto err_put; } spin_unlock_irqrestore(&apm->lock, flags); } else if (event == SND_SOC_DAPM_POST_PMD) { dev_info(adsp->dev, "disconnect event on APM %d, ADSP-FE %d\n", (i + 1) - APM_IN_START, w->reg); spin_lock_irqsave(&apm->fe_playback_lock, flags); if (!apm->fe_playback_triggered) { ret = 0; spin_unlock_irqrestore(&apm->fe_playback_lock, flags); goto err_put; } apm->fe_playback_triggered = -1; spin_unlock_irqrestore(&apm->fe_playback_lock, flags); ret = tegra210_adsp_send_state_msg(apm, nvfx_state_inactive, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(adsp->dev, "Failed to set state inactive."); goto err_put; } ret = tegra210_adsp_hv_pcm_trigger(adsp, apm->reg, /* apm_out_in */ SNDRV_PCM_STREAM_PLAYBACK, /* playback */ SNDRV_PCM_TRIGGER_STOP); /* PCM STOP */ if (ret < 0) { pr_err("%s: error during hv_pcm_trigger\n", __func__); goto err_put; } spin_lock_irqsave(&apm->lock, flags); prtd = apm->private_data; if (!prtd) { dev_dbg(adsp->dev, "PCM close caused this widget event\n"); spin_unlock_irqrestore(&apm->lock, flags); goto err_put; } runtime = prtd->substream->runtime; runtime->status->state = SNDRV_PCM_STATE_DISCONNECTED; wake_up(&runtime->sleep); wake_up(&runtime->tsleep); spin_unlock_irqrestore(&apm->lock, flags); } else { pr_err("%s: error. unknown event: %d\n", __func__, event); ret = -1; goto err_put; } err_put: pm_runtime_put(adsp->dev); return ret; } /* 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 = snd_soc_dapm_to_platform(w->dapm); struct tegra210_adsp *adsp = snd_soc_platform_get_drvdata(platform); struct tegra210_adsp_app *app; int ret = 0; 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)) { /* Request higher ADSP clock when starting stream. * Actmon takes care of adjusting frequency later. */ ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync error 0x%x\n", __func__, ret); return ret; } if (app->min_adsp_clock) adsp_update_dfs_min_rate(app->min_adsp_clock * 1000); ret = tegra210_adsp_send_hv_state_msg(adsp, app, nvfx_state_active, SNDRV_PCM_STREAM_PLAYBACK); if (ret < 0) dev_err(adsp->dev, "Failed to send hv state active msg."); ret = tegra210_adsp_send_state_msg(app, nvfx_state_active, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) dev_err(adsp->dev, "Failed to set state active."); ret = tegra210_adsp_send_hv_state_msg(adsp, app, nvfx_state_active, SNDRV_PCM_STREAM_CAPTURE); if (ret < 0) dev_err(adsp->dev, "Failed to send hv state active msg."); pm_runtime_put(adsp->dev); } } else { if (IS_APM_IN(w->reg)) { ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync error 0x%x\n", __func__, ret); return ret; } ret = tegra210_adsp_send_hv_state_msg(adsp, app, nvfx_state_inactive, SNDRV_PCM_STREAM_CAPTURE); if (ret < 0) dev_err(adsp->dev, "Failed to send hv state inactive msg."); ret = tegra210_adsp_send_state_msg(app, nvfx_state_inactive, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) dev_err(adsp->dev, "Failed to set state inactive."); ret = tegra210_adsp_send_reset_msg(app, (TEGRA210_ADSP_MSG_FLAG_SEND | TEGRA210_ADSP_MSG_FLAG_NEED_ACK)); if (ret < 0) dev_err(adsp->dev, "Failed to reset."); if (app->min_adsp_clock) adsp_update_dfs_min_rate(0); ret = tegra210_adsp_send_hv_state_msg(adsp, app, nvfx_state_inactive, SNDRV_PCM_STREAM_PLAYBACK); if (ret < 0) dev_err(adsp->dev, "Failed to send hv state inactive msg."); pm_runtime_put(adsp->dev); } } return ret; } static struct snd_soc_dai_ops tegra210_adsp_fe_dai_ops = { .hw_params = tegra210_adsp_fe_hw_params, .startup = tegra210_adsp_fe_startup, }; static struct snd_soc_dai_ops tegra210_adsp_admaif_dai_ops = { .hw_params = tegra210_adsp_admaif_hw_params, }; static struct snd_soc_dai_ops tegra210_adsp_eavb_dai_ops = { .hw_params = tegra210_adsp_eavb_hw_params, }; #define ADSP_PCM_DAI(id) \ { \ .name = "ADSP PCM" #id, \ .playback = { \ .stream_name = "ADSP PCM" #id " Receive", \ .channels_min = 1, \ .channels_max = 8, \ .rates = SNDRV_PCM_RATE_8000_48000, \ .formats = SNDRV_PCM_FMTBIT_S8 | \ SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE, \ }, \ .capture = { \ .stream_name = "ADSP PCM" #id " Transmit", \ .channels_min = 1, \ .channels_max = 8, \ .rates = SNDRV_PCM_RATE_8000_48000, \ .formats = SNDRV_PCM_FMTBIT_S8 | \ SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE, \ }, \ } #define ADSP_COMPR_DAI(id) \ { \ .name = "ADSP COMPR" #id, \ .compress_new = snd_soc_new_compress, \ .playback = { \ .stream_name = "ADSP COMPR" #id " Receive", \ .channels_min = 1, \ .channels_max = 2, \ .rates = SNDRV_PCM_RATE_8000_48000, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ } #define ADSP_EAVB_DAI() \ { \ .name = "ADSP EAVB", \ .playback = { \ .stream_name = "ADSP EAVB Transmit", \ .channels_min = 1, \ .channels_max = 2, \ .rates = SNDRV_PCM_RATE_8000_48000, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ .capture = { \ .stream_name = "ADSP EAVB Receive", \ .channels_min = 1, \ .channels_max = 2, \ .rates = SNDRV_PCM_RATE_8000_48000, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ } static struct snd_soc_dai_driver tegra210_adsp_dai[] = { ADSP_PCM_DAI(1), ADSP_PCM_DAI(2), ADSP_PCM_DAI(3), ADSP_PCM_DAI(4), ADSP_PCM_DAI(5), ADSP_PCM_DAI(6), ADSP_PCM_DAI(7), ADSP_PCM_DAI(8), ADSP_PCM_DAI(9), ADSP_PCM_DAI(10), ADSP_PCM_DAI(11), ADSP_PCM_DAI(12), ADSP_PCM_DAI(13), ADSP_PCM_DAI(14), ADSP_PCM_DAI(15), ADSP_COMPR_DAI(1), ADSP_COMPR_DAI(2), ADSP_EAVB_DAI(), }; #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 = 8, \ .rates = SNDRV_PCM_RATE_8000_48000, \ .formats = SNDRV_PCM_FMTBIT_S8 | \ SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE, \ }, \ .capture = { \ .stream_name = "ADSP-FE" #idx " Transmit",\ .channels_min = 1, \ .channels_max = 8, \ .rates = SNDRV_PCM_RATE_8000_48000, \ .formats = SNDRV_PCM_FMTBIT_S8 | \ SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE, \ }, \ .ops = &tegra210_adsp_fe_dai_ops, \ } #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 = 8, \ .rates = SNDRV_PCM_RATE_8000_48000, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ .capture = { \ .stream_name = "ADSP-ADMAIF" #idx " Transmit",\ .channels_min = 1, \ .channels_max = 8, \ .rates = SNDRV_PCM_RATE_8000_48000, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ .ops = &tegra210_adsp_admaif_dai_ops, \ } #define ADSP_EAVB_CODEC_DAI() \ { \ .name = "ADSP-EAVB", \ .id = ADSP_EAVB_START, \ .playback = { \ .stream_name = "ADSP-EAVB Receive", \ .channels_min = 1, \ .channels_max = 2, \ .rates = SNDRV_PCM_RATE_8000_48000, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ .capture = { \ .stream_name = "ADSP-EAVB Transmit", \ .channels_min = 1, \ .channels_max = 2, \ .rates = SNDRV_PCM_RATE_8000_48000, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ .ops = &tegra210_adsp_eavb_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_FE_CODEC_DAI(6), ADSP_FE_CODEC_DAI(7), ADSP_FE_CODEC_DAI(8), ADSP_FE_CODEC_DAI(9), ADSP_FE_CODEC_DAI(10), ADSP_FE_CODEC_DAI(11), ADSP_FE_CODEC_DAI(12), ADSP_FE_CODEC_DAI(13), ADSP_FE_CODEC_DAI(14), ADSP_FE_CODEC_DAI(15), ADSP_EAVB_CODEC_DAI(), 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), ADSP_ADMAIF_CODEC_DAI(11), ADSP_ADMAIF_CODEC_DAI(12), ADSP_ADMAIF_CODEC_DAI(13), ADSP_ADMAIF_CODEC_DAI(14), ADSP_ADMAIF_CODEC_DAI(15), ADSP_ADMAIF_CODEC_DAI(16), ADSP_ADMAIF_CODEC_DAI(17), ADSP_ADMAIF_CODEC_DAI(18), ADSP_ADMAIF_CODEC_DAI(19), ADSP_ADMAIF_CODEC_DAI(20), }; /* This array is linked with tegra210_adsp_virt_regs enum defines. Any thing changed in enum define should be also reflected here and vice-versa */ static const char *tegra210_adsp_mux_texts[] = { "None", "ADSP-FE1", "ADSP-FE2", "ADSP-FE3", "ADSP-FE4", "ADSP-FE5", "ADSP-FE6", "ADSP-FE7", "ADSP-FE8", "ADSP-FE9", "ADSP-FE10", "ADSP-FE11", "ADSP-FE12", "ADSP-FE13", "ADSP-FE14", "ADSP-FE15", "ADSP-EAVB", "ADSP-ADMAIF1", "ADSP-ADMAIF2", "ADSP-ADMAIF3", "ADSP-ADMAIF4", "ADSP-ADMAIF5", "ADSP-ADMAIF6", "ADSP-ADMAIF7", "ADSP-ADMAIF8", "ADSP-ADMAIF9", "ADSP-ADMAIF10", "ADSP-ADMAIF11", "ADSP-ADMAIF12", "ADSP-ADMAIF13", "ADSP-ADMAIF14", "ADSP-ADMAIF15", "ADSP-ADMAIF16", "ADSP-ADMAIF17", "ADSP-ADMAIF18", "ADSP-ADMAIF19", "ADSP-ADMAIF20", "NULL-SINK1", "NULL-SINK2", "NULL-SINK3", "NULL-SINK4", "NULL-SINK5", "NULL-SINK6", "NULL-SINK7", "NULL-SINK8", "NULL-SINK9", "NULL-SINK10", "NULL-SINK11", "NULL-SINK12", "NULL-SINK13", "NULL-SINK14", "NULL-SINK15", "APM-IN1", "APM-IN2", "APM-IN3", "APM-IN4", "APM-IN5", "APM-IN6", "APM-IN7", "APM-IN8", "APM-IN9", "APM-IN10", "APM-IN11", "APM-IN12", "APM-IN13", "APM-IN14", "APM-IN15", "APM-OUT1", "APM-OUT2", "APM-OUT3", "APM-OUT4", "APM-OUT5", "APM-OUT6", "APM-OUT7", "APM-OUT8", "APM-OUT9", "APM-OUT10", "APM-OUT11", "APM-OUT12", "APM-OUT13", "APM-OUT14", "APM-OUT15", "ADMA1", "ADMA2", "ADMA3", "ADMA4", "ADMA5", "ADMA6", "ADMA7", "ADMA8", "ADMA9", "ADMA10", "ADMA11", "ADMA12", "ADMA13", "ADMA14", "ADMA15", "ADMA1-TX", "ADMA2-TX", "ADMA3-TX", "ADMA4-TX", "ADMA5-TX", "ADMA6-TX", "ADMA7-TX", "ADMA8-TX", "ADMA9-TX", "ADMA10-TX", "ADMA11-TX", "ADMA12-TX", "ADMA13-TX", "ADMA14-TX", "ADMA15-TX", "PLUGIN1-PLACE-HOLDER", "PLUGIN2-PLACE-HOLDER", "PLUGIN3-PLACE-HOLDER", "PLUGIN4-PLACE-HOLDER", "PLUGIN5-PLACE-HOLDER", "PLUGIN6-PLACE-HOLDER", "PLUGIN7-PLACE-HOLDER", "PLUGIN8-PLACE-HOLDER", "PLUGIN9-PLACE-HOLDER", "PLUGIN10-PLACE-HOLDER", "PLUGIN11-PLACE-HOLDER", "PLUGIN12-PLACE-HOLDER", "PLUGIN13-PLACE-HOLDER", "PLUGIN14-PLACE-HOLDER", "PLUGIN15-PLACE-HOLDER", "PLUGIN16-PLACE-HOLDER", "PLUGIN17-PLACE-HOLDER", "PLUGIN18-PLACE-HOLDER", "PLUGIN19-PLACE-HOLDER", "PLUGIN20-PLACE-HOLDER", }; #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_fe6, TEGRA210_ADSP_FRONT_END6); static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe7, TEGRA210_ADSP_FRONT_END7); static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe8, TEGRA210_ADSP_FRONT_END8); static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe9, TEGRA210_ADSP_FRONT_END9); static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe10, TEGRA210_ADSP_FRONT_END10); static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe11, TEGRA210_ADSP_FRONT_END11); static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe12, TEGRA210_ADSP_FRONT_END12); static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe13, TEGRA210_ADSP_FRONT_END13); static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe14, TEGRA210_ADSP_FRONT_END14); static ADSP_MUX_ENUM_CTRL_DECL(adsp_fe15, TEGRA210_ADSP_FRONT_END15); static ADSP_MUX_ENUM_CTRL_DECL(adsp_eavb, TEGRA210_ADSP_EAVB); 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(adsp_admaif11, TEGRA210_ADSP_ADMAIF11); static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif12, TEGRA210_ADSP_ADMAIF12); static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif13, TEGRA210_ADSP_ADMAIF13); static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif14, TEGRA210_ADSP_ADMAIF14); static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif15, TEGRA210_ADSP_ADMAIF15); static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif16, TEGRA210_ADSP_ADMAIF16); static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif17, TEGRA210_ADSP_ADMAIF17); static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif18, TEGRA210_ADSP_ADMAIF18); static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif19, TEGRA210_ADSP_ADMAIF19); static ADSP_MUX_ENUM_CTRL_DECL(adsp_admaif20, TEGRA210_ADSP_ADMAIF20); static ADSP_MUX_ENUM_CTRL_DECL(null_sink1, TEGRA210_ADSP_NULL_SINK1); static ADSP_MUX_ENUM_CTRL_DECL(null_sink2, TEGRA210_ADSP_NULL_SINK2); static ADSP_MUX_ENUM_CTRL_DECL(null_sink3, TEGRA210_ADSP_NULL_SINK3); static ADSP_MUX_ENUM_CTRL_DECL(null_sink4, TEGRA210_ADSP_NULL_SINK4); static ADSP_MUX_ENUM_CTRL_DECL(null_sink5, TEGRA210_ADSP_NULL_SINK5); static ADSP_MUX_ENUM_CTRL_DECL(null_sink6, TEGRA210_ADSP_NULL_SINK6); static ADSP_MUX_ENUM_CTRL_DECL(null_sink7, TEGRA210_ADSP_NULL_SINK7); static ADSP_MUX_ENUM_CTRL_DECL(null_sink8, TEGRA210_ADSP_NULL_SINK8); static ADSP_MUX_ENUM_CTRL_DECL(null_sink9, TEGRA210_ADSP_NULL_SINK9); static ADSP_MUX_ENUM_CTRL_DECL(null_sink10, TEGRA210_ADSP_NULL_SINK10); static ADSP_MUX_ENUM_CTRL_DECL(null_sink11, TEGRA210_ADSP_NULL_SINK11); static ADSP_MUX_ENUM_CTRL_DECL(null_sink12, TEGRA210_ADSP_NULL_SINK12); static ADSP_MUX_ENUM_CTRL_DECL(null_sink13, TEGRA210_ADSP_NULL_SINK13); static ADSP_MUX_ENUM_CTRL_DECL(null_sink14, TEGRA210_ADSP_NULL_SINK14); static ADSP_MUX_ENUM_CTRL_DECL(null_sink15, TEGRA210_ADSP_NULL_SINK15); 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_in9, TEGRA210_ADSP_APM_IN9); static ADSP_MUX_ENUM_CTRL_DECL(apm_in10, TEGRA210_ADSP_APM_IN10); static ADSP_MUX_ENUM_CTRL_DECL(apm_in11, TEGRA210_ADSP_APM_IN11); static ADSP_MUX_ENUM_CTRL_DECL(apm_in12, TEGRA210_ADSP_APM_IN12); static ADSP_MUX_ENUM_CTRL_DECL(apm_in13, TEGRA210_ADSP_APM_IN13); static ADSP_MUX_ENUM_CTRL_DECL(apm_in14, TEGRA210_ADSP_APM_IN14); static ADSP_MUX_ENUM_CTRL_DECL(apm_in15, TEGRA210_ADSP_APM_IN15); 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(apm_out9, TEGRA210_ADSP_APM_OUT9); static ADSP_MUX_ENUM_CTRL_DECL(apm_out10, TEGRA210_ADSP_APM_OUT10); static ADSP_MUX_ENUM_CTRL_DECL(apm_out11, TEGRA210_ADSP_APM_OUT11); static ADSP_MUX_ENUM_CTRL_DECL(apm_out12, TEGRA210_ADSP_APM_OUT12); static ADSP_MUX_ENUM_CTRL_DECL(apm_out13, TEGRA210_ADSP_APM_OUT13); static ADSP_MUX_ENUM_CTRL_DECL(apm_out14, TEGRA210_ADSP_APM_OUT14); static ADSP_MUX_ENUM_CTRL_DECL(apm_out15, TEGRA210_ADSP_APM_OUT15); 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(adma5, TEGRA210_ADSP_PLUGIN_ADMA5); static ADSP_MUX_ENUM_CTRL_DECL(adma6, TEGRA210_ADSP_PLUGIN_ADMA6); static ADSP_MUX_ENUM_CTRL_DECL(adma7, TEGRA210_ADSP_PLUGIN_ADMA7); static ADSP_MUX_ENUM_CTRL_DECL(adma8, TEGRA210_ADSP_PLUGIN_ADMA8); static ADSP_MUX_ENUM_CTRL_DECL(adma9, TEGRA210_ADSP_PLUGIN_ADMA9); static ADSP_MUX_ENUM_CTRL_DECL(adma10, TEGRA210_ADSP_PLUGIN_ADMA10); static ADSP_MUX_ENUM_CTRL_DECL(adma11, TEGRA210_ADSP_PLUGIN_ADMA11); static ADSP_MUX_ENUM_CTRL_DECL(adma12, TEGRA210_ADSP_PLUGIN_ADMA12); static ADSP_MUX_ENUM_CTRL_DECL(adma13, TEGRA210_ADSP_PLUGIN_ADMA13); static ADSP_MUX_ENUM_CTRL_DECL(adma14, TEGRA210_ADSP_PLUGIN_ADMA14); static ADSP_MUX_ENUM_CTRL_DECL(adma15, TEGRA210_ADSP_PLUGIN_ADMA15); static ADSP_MUX_ENUM_CTRL_DECL(adma1_tx, TEGRA210_ADSP_PLUGIN_ADMA1_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma2_tx, TEGRA210_ADSP_PLUGIN_ADMA2_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma3_tx, TEGRA210_ADSP_PLUGIN_ADMA3_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma4_tx, TEGRA210_ADSP_PLUGIN_ADMA4_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma5_tx, TEGRA210_ADSP_PLUGIN_ADMA5_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma6_tx, TEGRA210_ADSP_PLUGIN_ADMA6_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma7_tx, TEGRA210_ADSP_PLUGIN_ADMA7_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma8_tx, TEGRA210_ADSP_PLUGIN_ADMA8_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma9_tx, TEGRA210_ADSP_PLUGIN_ADMA9_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma10_tx, TEGRA210_ADSP_PLUGIN_ADMA10_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma11_tx, TEGRA210_ADSP_PLUGIN_ADMA11_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma12_tx, TEGRA210_ADSP_PLUGIN_ADMA12_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma13_tx, TEGRA210_ADSP_PLUGIN_ADMA13_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma14_tx, TEGRA210_ADSP_PLUGIN_ADMA14_TX); static ADSP_MUX_ENUM_CTRL_DECL(adma15_tx, TEGRA210_ADSP_PLUGIN_ADMA15_TX); static ADSP_MUX_ENUM_CTRL_DECL(plugin1, TEGRA210_ADSP_PLUGIN1); static ADSP_MUX_ENUM_CTRL_DECL(plugin2, TEGRA210_ADSP_PLUGIN2); static ADSP_MUX_ENUM_CTRL_DECL(plugin3, TEGRA210_ADSP_PLUGIN3); static ADSP_MUX_ENUM_CTRL_DECL(plugin4, TEGRA210_ADSP_PLUGIN4); static ADSP_MUX_ENUM_CTRL_DECL(plugin5, TEGRA210_ADSP_PLUGIN5); static ADSP_MUX_ENUM_CTRL_DECL(plugin6, TEGRA210_ADSP_PLUGIN6); static ADSP_MUX_ENUM_CTRL_DECL(plugin7, TEGRA210_ADSP_PLUGIN7); static ADSP_MUX_ENUM_CTRL_DECL(plugin8, TEGRA210_ADSP_PLUGIN8); static ADSP_MUX_ENUM_CTRL_DECL(plugin9, TEGRA210_ADSP_PLUGIN9); static ADSP_MUX_ENUM_CTRL_DECL(plugin10, TEGRA210_ADSP_PLUGIN10); static ADSP_MUX_ENUM_CTRL_DECL(plugin11, TEGRA210_ADSP_PLUGIN11); static ADSP_MUX_ENUM_CTRL_DECL(plugin12, TEGRA210_ADSP_PLUGIN12); static ADSP_MUX_ENUM_CTRL_DECL(plugin13, TEGRA210_ADSP_PLUGIN13); static ADSP_MUX_ENUM_CTRL_DECL(plugin14, TEGRA210_ADSP_PLUGIN14); static ADSP_MUX_ENUM_CTRL_DECL(plugin15, TEGRA210_ADSP_PLUGIN15); static ADSP_MUX_ENUM_CTRL_DECL(plugin16, TEGRA210_ADSP_PLUGIN16); static ADSP_MUX_ENUM_CTRL_DECL(plugin17, TEGRA210_ADSP_PLUGIN17); static ADSP_MUX_ENUM_CTRL_DECL(plugin18, TEGRA210_ADSP_PLUGIN18); static ADSP_MUX_ENUM_CTRL_DECL(plugin19, TEGRA210_ADSP_PLUGIN19); static ADSP_MUX_ENUM_CTRL_DECL(plugin20, TEGRA210_ADSP_PLUGIN20); #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_FE_WIDGETS(sname, ename, reg) \ SND_SOC_DAPM_AIF_IN_E(sname " RX", NULL, 0, reg, \ TEGRA210_ADSP_WIDGET_EN_SHIFT, 0, tegra210_adsp_fe_widget_event, \ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), \ SND_SOC_DAPM_AIF_OUT_E(sname " TX", NULL, 0, reg, \ TEGRA210_ADSP_WIDGET_EN_SHIFT, 0, NULL, \ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), \ 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) #define SND_SOC_DAPM_IN(wname, wreg) \ { .id = snd_soc_dapm_spk, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = wreg, \ .event = tegra210_adsp_null_sink_hw_params, \ .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD } static struct snd_soc_dapm_widget tegra210_adsp_widgets[] = { ADSP_FE_WIDGETS("ADSP-FE1", adsp_fe1, TEGRA210_ADSP_FRONT_END1), ADSP_FE_WIDGETS("ADSP-FE2", adsp_fe2, TEGRA210_ADSP_FRONT_END2), ADSP_FE_WIDGETS("ADSP-FE3", adsp_fe3, TEGRA210_ADSP_FRONT_END3), ADSP_FE_WIDGETS("ADSP-FE4", adsp_fe4, TEGRA210_ADSP_FRONT_END4), ADSP_FE_WIDGETS("ADSP-FE5", adsp_fe5, TEGRA210_ADSP_FRONT_END5), ADSP_FE_WIDGETS("ADSP-FE6", adsp_fe6, TEGRA210_ADSP_FRONT_END6), ADSP_FE_WIDGETS("ADSP-FE7", adsp_fe7, TEGRA210_ADSP_FRONT_END7), ADSP_FE_WIDGETS("ADSP-FE8", adsp_fe8, TEGRA210_ADSP_FRONT_END8), ADSP_FE_WIDGETS("ADSP-FE9", adsp_fe9, TEGRA210_ADSP_FRONT_END9), ADSP_FE_WIDGETS("ADSP-FE10", adsp_fe10, TEGRA210_ADSP_FRONT_END10), ADSP_FE_WIDGETS("ADSP-FE11", adsp_fe11, TEGRA210_ADSP_FRONT_END11), ADSP_FE_WIDGETS("ADSP-FE12", adsp_fe12, TEGRA210_ADSP_FRONT_END12), ADSP_FE_WIDGETS("ADSP-FE13", adsp_fe13, TEGRA210_ADSP_FRONT_END13), ADSP_FE_WIDGETS("ADSP-FE14", adsp_fe14, TEGRA210_ADSP_FRONT_END14), ADSP_FE_WIDGETS("ADSP-FE15", adsp_fe15, TEGRA210_ADSP_FRONT_END15), ADSP_EP_WIDGETS("ADSP-EAVB", adsp_eavb), 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_EP_WIDGETS("ADSP-ADMAIF11", adsp_admaif11), ADSP_EP_WIDGETS("ADSP-ADMAIF12", adsp_admaif12), ADSP_EP_WIDGETS("ADSP-ADMAIF13", adsp_admaif13), ADSP_EP_WIDGETS("ADSP-ADMAIF14", adsp_admaif14), ADSP_EP_WIDGETS("ADSP-ADMAIF15", adsp_admaif15), ADSP_EP_WIDGETS("ADSP-ADMAIF16", adsp_admaif16), ADSP_EP_WIDGETS("ADSP-ADMAIF17", adsp_admaif17), ADSP_EP_WIDGETS("ADSP-ADMAIF18", adsp_admaif18), ADSP_EP_WIDGETS("ADSP-ADMAIF19", adsp_admaif19), ADSP_EP_WIDGETS("ADSP-ADMAIF20", adsp_admaif20), ADSP_EP_WIDGETS("NULL-SINK1", null_sink1), ADSP_EP_WIDGETS("NULL-SINK2", null_sink2), ADSP_EP_WIDGETS("NULL-SINK3", null_sink3), ADSP_EP_WIDGETS("NULL-SINK4", null_sink4), ADSP_EP_WIDGETS("NULL-SINK5", null_sink5), ADSP_EP_WIDGETS("NULL-SINK6", null_sink6), ADSP_EP_WIDGETS("NULL-SINK7", null_sink7), ADSP_EP_WIDGETS("NULL-SINK8", null_sink8), ADSP_EP_WIDGETS("NULL-SINK9", null_sink9), ADSP_EP_WIDGETS("NULL-SINK10", null_sink10), ADSP_EP_WIDGETS("NULL-SINK11", null_sink11), ADSP_EP_WIDGETS("NULL-SINK12", null_sink12), ADSP_EP_WIDGETS("NULL-SINK13", null_sink13), ADSP_EP_WIDGETS("NULL-SINK14", null_sink14), ADSP_EP_WIDGETS("NULL-SINK15", null_sink15), SND_SOC_DAPM_IN("NULL-SINK1", TEGRA210_ADSP_NULL_SINK1), SND_SOC_DAPM_IN("NULL-SINK2", TEGRA210_ADSP_NULL_SINK2), SND_SOC_DAPM_IN("NULL-SINK3", TEGRA210_ADSP_NULL_SINK3), SND_SOC_DAPM_IN("NULL-SINK4", TEGRA210_ADSP_NULL_SINK4), SND_SOC_DAPM_IN("NULL-SINK5", TEGRA210_ADSP_NULL_SINK5), SND_SOC_DAPM_IN("NULL-SINK6", TEGRA210_ADSP_NULL_SINK6), SND_SOC_DAPM_IN("NULL-SINK7", TEGRA210_ADSP_NULL_SINK7), SND_SOC_DAPM_IN("NULL-SINK8", TEGRA210_ADSP_NULL_SINK8), SND_SOC_DAPM_IN("NULL-SINK9", TEGRA210_ADSP_NULL_SINK9), SND_SOC_DAPM_IN("NULL-SINK10", TEGRA210_ADSP_NULL_SINK10), SND_SOC_DAPM_IN("NULL-SINK11", TEGRA210_ADSP_NULL_SINK11), SND_SOC_DAPM_IN("NULL-SINK12", TEGRA210_ADSP_NULL_SINK12), SND_SOC_DAPM_IN("NULL-SINK13", TEGRA210_ADSP_NULL_SINK13), SND_SOC_DAPM_IN("NULL-SINK14", TEGRA210_ADSP_NULL_SINK14), SND_SOC_DAPM_IN("NULL-SINK15", TEGRA210_ADSP_NULL_SINK15), 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-IN9", apm_in9, TEGRA210_ADSP_APM_IN9), ADSP_WIDGETS("APM-IN10", apm_in10, TEGRA210_ADSP_APM_IN10), ADSP_WIDGETS("APM-IN11", apm_in11, TEGRA210_ADSP_APM_IN11), ADSP_WIDGETS("APM-IN12", apm_in12, TEGRA210_ADSP_APM_IN12), ADSP_WIDGETS("APM-IN13", apm_in13, TEGRA210_ADSP_APM_IN13), ADSP_WIDGETS("APM-IN14", apm_in14, TEGRA210_ADSP_APM_IN14), ADSP_WIDGETS("APM-IN15", apm_in15, TEGRA210_ADSP_APM_IN15), 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("APM-OUT9", apm_out9, TEGRA210_ADSP_APM_OUT9), ADSP_WIDGETS("APM-OUT10", apm_out10, TEGRA210_ADSP_APM_OUT10), ADSP_WIDGETS("APM-OUT11", apm_out11, TEGRA210_ADSP_APM_OUT11), ADSP_WIDGETS("APM-OUT12", apm_out12, TEGRA210_ADSP_APM_OUT12), ADSP_WIDGETS("APM-OUT13", apm_out13, TEGRA210_ADSP_APM_OUT13), ADSP_WIDGETS("APM-OUT14", apm_out14, TEGRA210_ADSP_APM_OUT14), ADSP_WIDGETS("APM-OUT15", apm_out15, TEGRA210_ADSP_APM_OUT15), 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("ADMA5", adma5, TEGRA210_ADSP_PLUGIN_ADMA5), ADSP_WIDGETS("ADMA6", adma6, TEGRA210_ADSP_PLUGIN_ADMA6), ADSP_WIDGETS("ADMA7", adma7, TEGRA210_ADSP_PLUGIN_ADMA7), ADSP_WIDGETS("ADMA8", adma8, TEGRA210_ADSP_PLUGIN_ADMA8), ADSP_WIDGETS("ADMA9", adma9, TEGRA210_ADSP_PLUGIN_ADMA9), ADSP_WIDGETS("ADMA10", adma10, TEGRA210_ADSP_PLUGIN_ADMA10), ADSP_WIDGETS("ADMA11", adma11, TEGRA210_ADSP_PLUGIN_ADMA11), ADSP_WIDGETS("ADMA12", adma12, TEGRA210_ADSP_PLUGIN_ADMA12), ADSP_WIDGETS("ADMA13", adma13, TEGRA210_ADSP_PLUGIN_ADMA13), ADSP_WIDGETS("ADMA14", adma14, TEGRA210_ADSP_PLUGIN_ADMA14), ADSP_WIDGETS("ADMA15", adma15, TEGRA210_ADSP_PLUGIN_ADMA15), ADSP_WIDGETS("ADMA1-TX", adma1_tx, TEGRA210_ADSP_PLUGIN_ADMA1_TX), ADSP_WIDGETS("ADMA2-TX", adma2_tx, TEGRA210_ADSP_PLUGIN_ADMA2_TX), ADSP_WIDGETS("ADMA3-TX", adma3_tx, TEGRA210_ADSP_PLUGIN_ADMA3_TX), ADSP_WIDGETS("ADMA4-TX", adma4_tx, TEGRA210_ADSP_PLUGIN_ADMA4_TX), ADSP_WIDGETS("ADMA5-TX", adma5_tx, TEGRA210_ADSP_PLUGIN_ADMA5_TX), ADSP_WIDGETS("ADMA6-TX", adma6_tx, TEGRA210_ADSP_PLUGIN_ADMA6_TX), ADSP_WIDGETS("ADMA7-TX", adma7_tx, TEGRA210_ADSP_PLUGIN_ADMA7_TX), ADSP_WIDGETS("ADMA8-TX", adma8_tx, TEGRA210_ADSP_PLUGIN_ADMA8_TX), ADSP_WIDGETS("ADMA9-TX", adma9_tx, TEGRA210_ADSP_PLUGIN_ADMA9_TX), ADSP_WIDGETS("ADMA10-TX", adma10_tx, TEGRA210_ADSP_PLUGIN_ADMA10_TX), ADSP_WIDGETS("ADMA11-TX", adma11_tx, TEGRA210_ADSP_PLUGIN_ADMA11_TX), ADSP_WIDGETS("ADMA12-TX", adma12_tx, TEGRA210_ADSP_PLUGIN_ADMA12_TX), ADSP_WIDGETS("ADMA13-TX", adma13_tx, TEGRA210_ADSP_PLUGIN_ADMA13_TX), ADSP_WIDGETS("ADMA14-TX", adma14_tx, TEGRA210_ADSP_PLUGIN_ADMA14_TX), ADSP_WIDGETS("ADMA15-TX", adma15_tx, TEGRA210_ADSP_PLUGIN_ADMA15_TX), ADSP_WIDGETS("PLUGIN1-PLACE-HOLDER", plugin1, TEGRA210_ADSP_PLUGIN1), ADSP_WIDGETS("PLUGIN2-PLACE-HOLDER", plugin2, TEGRA210_ADSP_PLUGIN2), ADSP_WIDGETS("PLUGIN3-PLACE-HOLDER", plugin3, TEGRA210_ADSP_PLUGIN3), ADSP_WIDGETS("PLUGIN4-PLACE-HOLDER", plugin4, TEGRA210_ADSP_PLUGIN4), ADSP_WIDGETS("PLUGIN5-PLACE-HOLDER", plugin5, TEGRA210_ADSP_PLUGIN5), ADSP_WIDGETS("PLUGIN6-PLACE-HOLDER", plugin6, TEGRA210_ADSP_PLUGIN6), ADSP_WIDGETS("PLUGIN7-PLACE-HOLDER", plugin7, TEGRA210_ADSP_PLUGIN7), ADSP_WIDGETS("PLUGIN8-PLACE-HOLDER", plugin8, TEGRA210_ADSP_PLUGIN8), ADSP_WIDGETS("PLUGIN9-PLACE-HOLDER", plugin9, TEGRA210_ADSP_PLUGIN9), ADSP_WIDGETS("PLUGIN10-PLACE-HOLDER", plugin10, TEGRA210_ADSP_PLUGIN10), ADSP_WIDGETS("PLUGIN11-PLACE-HOLDER", plugin11, TEGRA210_ADSP_PLUGIN11), ADSP_WIDGETS("PLUGIN12-PLACE-HOLDER", plugin12, TEGRA210_ADSP_PLUGIN12), ADSP_WIDGETS("PLUGIN13-PLACE-HOLDER", plugin13, TEGRA210_ADSP_PLUGIN13), ADSP_WIDGETS("PLUGIN14-PLACE-HOLDER", plugin14, TEGRA210_ADSP_PLUGIN14), ADSP_WIDGETS("PLUGIN15-PLACE-HOLDER", plugin15, TEGRA210_ADSP_PLUGIN15), ADSP_WIDGETS("PLUGIN16-PLACE-HOLDER", plugin16, TEGRA210_ADSP_PLUGIN16), ADSP_WIDGETS("PLUGIN17-PLACE-HOLDER", plugin17, TEGRA210_ADSP_PLUGIN17), ADSP_WIDGETS("PLUGIN18-PLACE-HOLDER", plugin18, TEGRA210_ADSP_PLUGIN18), ADSP_WIDGETS("PLUGIN19-PLACE-HOLDER", plugin19, TEGRA210_ADSP_PLUGIN19), ADSP_WIDGETS("PLUGIN20-PLACE-HOLDER", plugin20, TEGRA210_ADSP_PLUGIN20), }; #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-FE6", "ADSP-FE6 RX"}, \ { name " MUX", "ADSP-FE7", "ADSP-FE7 RX"}, \ { name " MUX", "ADSP-FE8", "ADSP-FE8 RX"}, \ { name " MUX", "ADSP-FE9", "ADSP-FE9 RX"}, \ { name " MUX", "ADSP-FE10", "ADSP-FE10 RX"}, \ { name " MUX", "ADSP-FE11", "ADSP-FE11 RX"}, \ { name " MUX", "ADSP-FE12", "ADSP-FE12 RX"}, \ { name " MUX", "ADSP-FE13", "ADSP-FE13 RX"}, \ { name " MUX", "ADSP-FE14", "ADSP-FE14 RX"}, \ { name " MUX", "ADSP-FE15", "ADSP-FE15 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"}, \ { name " MUX", "ADSP-ADMAIF11", "ADSP-ADMAIF11 RX"}, \ { name " MUX", "ADSP-ADMAIF12", "ADSP-ADMAIF12 RX"}, \ { name " MUX", "ADSP-ADMAIF13", "ADSP-ADMAIF13 RX"}, \ { name " MUX", "ADSP-ADMAIF14", "ADSP-ADMAIF14 RX"}, \ { name " MUX", "ADSP-ADMAIF15", "ADSP-ADMAIF15 RX"}, \ { name " MUX", "ADSP-ADMAIF16", "ADSP-ADMAIF16 RX"}, \ { name " MUX", "ADSP-ADMAIF17", "ADSP-ADMAIF17 RX"}, \ { name " MUX", "ADSP-ADMAIF18", "ADSP-ADMAIF18 RX"}, \ { name " MUX", "ADSP-ADMAIF19", "ADSP-ADMAIF19 RX"}, \ { name " MUX", "ADSP-ADMAIF20", "ADSP-ADMAIF20 RX"}, \ { name " MUX", "ADSP-EAVB", "ADSP-EAVB 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"}, \ { name " MUX", "APM-IN9", "APM-IN9 TX"}, \ { name " MUX", "APM-IN10", "APM-IN10 TX"}, \ { name " MUX", "APM-IN11", "APM-IN11 TX"}, \ { name " MUX", "APM-IN12", "APM-IN12 TX"}, \ { name " MUX", "APM-IN13", "APM-IN13 TX"}, \ { name " MUX", "APM-IN14", "APM-IN14 TX"}, \ { name " MUX", "APM-IN15", "APM-IN15 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"}, \ { name " MUX", "APM-OUT9", "APM-OUT9 TX"}, \ { name " MUX", "APM-OUT10", "APM-OUT10 TX"}, \ { name " MUX", "APM-OUT11", "APM-OUT11 TX"}, \ { name " MUX", "APM-OUT12", "APM-OUT12 TX"}, \ { name " MUX", "APM-OUT13", "APM-OUT13 TX"}, \ { name " MUX", "APM-OUT14", "APM-OUT14 TX"}, \ { name " MUX", "APM-OUT15", "APM-OUT15 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"}, \ { name " MUX", "ADMA5", "ADMA5 TX"}, \ { name " MUX", "ADMA6", "ADMA6 TX"}, \ { name " MUX", "ADMA7", "ADMA7 TX"}, \ { name " MUX", "ADMA8", "ADMA8 TX"}, \ { name " MUX", "ADMA9", "ADMA9 TX"}, \ { name " MUX", "ADMA10", "ADMA10 TX"}, \ { name " MUX", "ADMA11", "ADMA11 TX"}, \ { name " MUX", "ADMA12", "ADMA12 TX"}, \ { name " MUX", "ADMA13", "ADMA13 TX"}, \ { name " MUX", "ADMA14", "ADMA14 TX"}, \ { name " MUX", "ADMA15", "ADMA15 TX"}, \ { name " MUX", "ADMA1-TX", "ADMA1-TX TX"}, \ { name " MUX", "ADMA2-TX", "ADMA2-TX TX"}, \ { name " MUX", "ADMA3-TX", "ADMA3-TX TX"}, \ { name " MUX", "ADMA4-TX", "ADMA4-TX TX"}, \ { name " MUX", "ADMA5-TX", "ADMA5-TX TX"}, \ { name " MUX", "ADMA6-TX", "ADMA6-TX TX"}, \ { name " MUX", "ADMA7-TX", "ADMA7-TX TX"}, \ { name " MUX", "ADMA8-TX", "ADMA8-TX TX"}, \ { name " MUX", "ADMA9-TX", "ADMA9-TX TX"}, \ { name " MUX", "ADMA10-TX", "ADMA10-TX TX"}, \ { name " MUX", "ADMA11-TX", "ADMA11-TX TX"}, \ { name " MUX", "ADMA12-TX", "ADMA12-TX TX"}, \ { name " MUX", "ADMA13-TX", "ADMA13-TX TX"}, \ { name " MUX", "ADMA14-TX", "ADMA14-TX TX"}, \ { name " MUX", "ADMA15-TX", "ADMA15-TX TX"} #define ADSP_PLUGIN_ROUTES(name) \ { name " MUX", "PLUGIN1-PLACE-HOLDER", "PLUGIN1-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN2-PLACE-HOLDER", "PLUGIN2-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN3-PLACE-HOLDER", "PLUGIN3-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN4-PLACE-HOLDER", "PLUGIN4-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN5-PLACE-HOLDER", "PLUGIN5-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN6-PLACE-HOLDER", "PLUGIN6-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN7-PLACE-HOLDER", "PLUGIN7-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN8-PLACE-HOLDER", "PLUGIN8-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN9-PLACE-HOLDER", "PLUGIN9-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN10-PLACE-HOLDER", "PLUGIN10-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN11-PLACE-HOLDER", "PLUGIN11-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN12-PLACE-HOLDER", "PLUGIN12-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN13-PLACE-HOLDER", "PLUGIN13-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN14-PLACE-HOLDER", "PLUGIN14-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN15-PLACE-HOLDER", "PLUGIN15-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN16-PLACE-HOLDER", "PLUGIN16-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN17-PLACE-HOLDER", "PLUGIN17-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN18-PLACE-HOLDER", "PLUGIN18-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN19-PLACE-HOLDER", "PLUGIN19-PLACE-HOLDER TX"}, \ { name " MUX", "PLUGIN20-PLACE-HOLDER", "PLUGIN20-PLACE-HOLDER 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_EP_MUX_ROUTES_NULL_SINK(name) \ { name " TX", NULL, name " MUX"}, \ { name, NULL, name " TX"}, \ 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_PLUGIN_ROUTES(name) #define ADSP_PLUGIN_MUX_ROUTES(name) \ { name " TX", NULL, name " MUX"}, \ ADSP_APM_IN_ROUTES(name), \ ADSP_PLUGIN_ROUTES(name), \ ADSP_ADMA_ROUTES(name) #define ADSP_ADMA_MUX_ROUTES(name) \ { name " TX", NULL, name " MUX"}, \ ADSP_APM_IN_ROUTES(name), \ ADSP_PLUGIN_ROUTES(name) #define ADSP_ADMA_OUT_MUX_ROUTES(name) \ { name " TX", NULL, name " MUX"}, \ ADSP_APM_IN_ROUTES(name), \ ADSP_PLUGIN_ROUTES(name) static 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-FE6"), ADSP_EP_MUX_ROUTES("ADSP-FE7"), ADSP_EP_MUX_ROUTES("ADSP-FE8"), ADSP_EP_MUX_ROUTES("ADSP-FE9"), ADSP_EP_MUX_ROUTES("ADSP-FE10"), ADSP_EP_MUX_ROUTES("ADSP-FE11"), ADSP_EP_MUX_ROUTES("ADSP-FE12"), ADSP_EP_MUX_ROUTES("ADSP-FE13"), ADSP_EP_MUX_ROUTES("ADSP-FE14"), ADSP_EP_MUX_ROUTES("ADSP-FE15"), 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_EP_MUX_ROUTES("ADSP-ADMAIF11"), ADSP_EP_MUX_ROUTES("ADSP-ADMAIF12"), ADSP_EP_MUX_ROUTES("ADSP-ADMAIF13"), ADSP_EP_MUX_ROUTES("ADSP-ADMAIF14"), ADSP_EP_MUX_ROUTES("ADSP-ADMAIF15"), ADSP_EP_MUX_ROUTES("ADSP-ADMAIF16"), ADSP_EP_MUX_ROUTES("ADSP-ADMAIF17"), ADSP_EP_MUX_ROUTES("ADSP-ADMAIF18"), ADSP_EP_MUX_ROUTES("ADSP-ADMAIF19"), ADSP_EP_MUX_ROUTES("ADSP-ADMAIF20"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK1"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK2"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK3"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK4"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK5"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK6"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK7"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK8"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK9"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK10"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK11"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK12"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK13"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK14"), ADSP_EP_MUX_ROUTES_NULL_SINK("NULL-SINK15"), ADSP_EP_MUX_ROUTES("ADSP-EAVB"), 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_IN_MUX_ROUTES("APM-IN9"), ADSP_APM_IN_MUX_ROUTES("APM-IN10"), ADSP_APM_IN_MUX_ROUTES("APM-IN11"), ADSP_APM_IN_MUX_ROUTES("APM-IN12"), ADSP_APM_IN_MUX_ROUTES("APM-IN13"), ADSP_APM_IN_MUX_ROUTES("APM-IN14"), ADSP_APM_IN_MUX_ROUTES("APM-IN15"), 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_APM_OUT_MUX_ROUTES("APM-OUT9"), ADSP_APM_OUT_MUX_ROUTES("APM-OUT10"), ADSP_APM_OUT_MUX_ROUTES("APM-OUT11"), ADSP_APM_OUT_MUX_ROUTES("APM-OUT12"), ADSP_APM_OUT_MUX_ROUTES("APM-OUT13"), ADSP_APM_OUT_MUX_ROUTES("APM-OUT14"), ADSP_APM_OUT_MUX_ROUTES("APM-OUT15"), ADSP_ADMA_MUX_ROUTES("ADMA1"), ADSP_ADMA_MUX_ROUTES("ADMA2"), ADSP_ADMA_MUX_ROUTES("ADMA3"), ADSP_ADMA_MUX_ROUTES("ADMA4"), ADSP_ADMA_MUX_ROUTES("ADMA5"), ADSP_ADMA_MUX_ROUTES("ADMA6"), ADSP_ADMA_MUX_ROUTES("ADMA7"), ADSP_ADMA_MUX_ROUTES("ADMA8"), ADSP_ADMA_MUX_ROUTES("ADMA9"), ADSP_ADMA_MUX_ROUTES("ADMA10"), ADSP_ADMA_MUX_ROUTES("ADMA11"), ADSP_ADMA_MUX_ROUTES("ADMA12"), ADSP_ADMA_MUX_ROUTES("ADMA13"), ADSP_ADMA_MUX_ROUTES("ADMA14"), ADSP_ADMA_MUX_ROUTES("ADMA15"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA1-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA2-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA3-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA4-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA5-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA6-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA7-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA8-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA9-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA10-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA11-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA12-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA13-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA14-TX"), ADSP_ADMA_OUT_MUX_ROUTES("ADMA15-TX"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN1-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN2-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN3-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN4-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN5-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN6-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN7-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN8-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN9-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN10-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN11-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN12-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN13-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN14-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN15-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN16-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN17-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN18-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN19-PLACE-HOLDER"), ADSP_PLUGIN_MUX_ROUTES("PLUGIN20-PLACE-HOLDER"), }; static void tegra210_adsp_wt_replace(struct device *dev, char **dest1, const char *src) { char *dest = NULL; if (!*dest1 || !src) return; dest = (char *)devm_kzalloc(dev, strlen(src) + 5, GFP_KERNEL); if (strstr(*dest1, " TX")) { strcpy(dest, src); strcat(dest, " TX"); *dest1 = dest; } else if (strstr(*dest1, " RX")) { strcpy(dest, src); strcat(dest, " RX"); *dest1 = dest; } else if (strstr(*dest1, " MUX")) { strcpy(dest, src); strcat(dest, " MUX"); *dest1 = dest; } else if (strstr(*dest1, " Transmit")) ;/* do nothing */ else if (strstr(*dest1, " Receive")) ;/* do nothing */ else { strcpy(dest, src); *dest1 = dest; } } static void tegra210_adsp_route_modify(struct device *dev, const char *wt_default, const char *wt_from_dt) { int i, len, ret; if (!wt_default || !wt_from_dt) return; ret = device_property_read_string_array(dev, "fe-info", NULL, 0); len = strlen(wt_default); /* Modify dapm routing table */ for (i = ((ret > 0) ? 0 : TEGRA210_ADSP_ROUTE_BASE); i < ARRAY_SIZE(tegra210_adsp_routes); i++) { /* replace sink name */ if (tegra210_adsp_routes[i].sink) if (strstr(tegra210_adsp_routes[i].sink, wt_default) && (tegra210_adsp_routes[i].sink[len] == ' ' || tegra210_adsp_routes[i].sink[len] == 0)) tegra210_adsp_wt_replace(dev, (char **)&tegra210_adsp_routes[i].sink, wt_from_dt); /* replace control name */ if (tegra210_adsp_routes[i].control) if (strstr(tegra210_adsp_routes[i].control, wt_default) && (tegra210_adsp_routes[i].control[len] == ' ' || tegra210_adsp_routes[i].control[len] == 0)) tegra210_adsp_wt_replace(dev, (char **)&tegra210_adsp_routes[i].control, wt_from_dt); /* replace source name */ if (tegra210_adsp_routes[i].source) if (strstr(tegra210_adsp_routes[i].source, wt_default) && (tegra210_adsp_routes[i].source[len] == ' ' || tegra210_adsp_routes[i].source[len] == 0)) tegra210_adsp_wt_replace(dev, (char **)&tegra210_adsp_routes[i].source, wt_from_dt); } } static void adsp_fe_name_override(struct device *dev, int count) { int ret = 0, i; const char **fe_names; char *name; fe_names = devm_kcalloc(dev, count, sizeof(*fe_names), GFP_KERNEL); if (!fe_names) return; ret = device_property_read_string_array(dev, "fe-info", fe_names, count); ret = ret > ADSP_FE_END ? ADSP_FE_END : ret; for (i = 0 ; i < ret; i++) { tegra210_adsp_route_modify(dev, tegra210_adsp_mux_texts[1+i], fe_names[i]); name = devm_kzalloc(dev, strlen(fe_names[i]) + 3, GFP_KERNEL); strcpy((char *)name, fe_names[i]); strcat((char *)name, " TX"); tegra210_adsp_widgets[(3*i)+1].name = name; name = devm_kzalloc(dev, strlen(fe_names[i]) + 4, GFP_KERNEL); strcpy((char *)name, fe_names[i]); strcat((char *)name, " MUX"); tegra210_adsp_widgets[(3*i)+2].name = name; name = devm_kzalloc(dev, strlen(fe_names[i]) + 3, GFP_KERNEL); strcpy((char *)name, fe_names[i]); strcat((char *)name, " RX"); tegra210_adsp_widgets[(3*i)].name = name; tegra210_adsp_mux_texts[1+i] = fe_names[i]; } } static int tegra210_adsp_param_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct soc_bytes *params = (void *)kcontrol->private_value; if (params->mask == SNDRV_CTL_ELEM_TYPE_INTEGER) { params->num_regs = 128; uinfo->value.integer.min = 0; uinfo->value.integer.max = 0xffffffff; } uinfo->type = params->mask; uinfo->count = params->num_regs; return 0; } static int tegra_adsp_get_connected_adma(struct tegra210_adsp *adsp, uint32_t fe_reg) { int32_t i, src; struct tegra210_adsp_app *app = NULL; for (i = ADMA_START; i <= ADMA_TX_END; i++) { app = &adsp->apps[i]; src = app->reg; while (!IS_ADSP_FE(src) && src != 0) { src = tegra210_adsp_get_source( adsp, src); } if (src != fe_reg) continue; return i; dev_vdbg(adsp->dev, "%s : connected adma %d to fe %d\n", __func__, i, fe_reg); } return 0; } static int tegra210_adsp_get_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct soc_enum *control = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_adsp *adsp = snd_soc_component_get_drvdata(cmpnt); struct tegra210_adsp_switch *sch = &adsp->switches[control->reg - 1]; ucontrol->value.integer.value[0] = sch->active_fe; return 0; } static int tegra210_adsp_set_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct soc_enum *control = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_adsp *adsp = snd_soc_component_get_drvdata(cmpnt); struct tegra210_adsp_switch *sch = &adsp->switches[control->reg - 1]; uint32_t fe_reg = (uint32_t)ucontrol->value.integer.value[0]; uint32_t conn_adma_id = 0, curr_adma_id; struct tegra210_adsp_app *app_new = NULL, *app_curr = NULL, *app = NULL; int32_t num_params, ret = 0; uint32_t source, admaif_id, be_reg, active_be_reg; apm_msg_t apm_msg_new, apm_msg_curr; nvfx_adma_init_params_t adma_params; struct device *dev = adsp->dev; struct device_node *node = dev->of_node; unsigned long flags; if (!adsp->init_done) { dev_warn(adsp->dev, "ADSP is not booted yet\n"); return -EPERM; } admaif_id = sch->admaif_id; be_reg = tegra_adsp_get_connected_be(adsp, fe_reg, SNDRV_PCM_STREAM_PLAYBACK); /* request to move all switch input to drain mode */ if (fe_reg == 0) goto stop_playback; if (!IS_ADSP_FE(fe_reg) || !IS_VALID_INPUT(fe_reg, sch->allowed_fe)) { dev_err(adsp->dev, "Invalid FE\n"); return -EINVAL; } if ((sch->active_fe == fe_reg) && (adsp->fe_to_admaif_map[sch->active_fe - 1] [SNDRV_PCM_STREAM_PLAYBACK])) { dev_info(adsp->dev, "Switch input already active\n"); return 0; } if (!IS_NULL_SINK(be_reg)) { dev_err(adsp->dev, "%s : fe ---> be path incomplete", __func__); return -EINVAL; } if (of_device_is_compatible(node, "nvidia,tegra210-adsp-audio-hv")) { ret = tegra210_adsp_admaif_hv_hw_params(adsp, NULL, admaif_id, be_reg, &adma_params, SNDRV_PCM_STREAM_CAPTURE); if (ret < 0) { pr_err("%s: error during adsp_admaif_hv_hw_params\n", __func__); return -EPERM; } } adma_params.mode = ADMA_MODE_CONTINUOUS; adma_params.ahub_channel = admaif_id; adma_params.periods = 4; adma_params.adma_ch_page = adsp->adma_ch_page; 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; adma_params.adma_channel = app->adma_chan; 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, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { dev_err(adsp->dev, "ADMA params msg failed. %d.", ret); return ret; } conn_adma_id = tegra_adsp_get_connected_adma(adsp, fe_reg); if (!conn_adma_id) { dev_vdbg(adsp->dev, "%s : fe%d not connected\n", __func__, fe_reg); return -EINVAL; } stop_playback: if (sch->active_fe && (sch->active_fe != fe_reg)) { active_be_reg = tegra_adsp_get_connected_be(adsp, sch->active_fe, SNDRV_PCM_STREAM_PLAYBACK); if (!IS_NULL_SINK(active_be_reg)) dev_err(adsp->dev, "%s : fe ---> be path incomplete", __func__); /* skip if fe is active but playback has finished */ spin_lock_irqsave(&adsp->switch_lock, flags); if ((adsp->fe_to_admaif_map[sch->active_fe - 1] [SNDRV_PCM_STREAM_PLAYBACK]) == 0 || !adsp->is_fe_set[sch->active_fe - 1]) { tegra_adsp_set_admaif_id(adsp, 0, active_be_reg, SNDRV_PCM_STREAM_CAPTURE); spin_unlock_irqrestore(&adsp->switch_lock, flags); goto start_playback; } /* This is needed so that stop trigger is ignored for be_reg */ tegra_adsp_set_admaif_id(adsp, 0, active_be_reg, SNDRV_PCM_STREAM_CAPTURE); spin_unlock_irqrestore(&adsp->switch_lock, flags); tegra_ivc_stop_playback(adsp, admaif_id - 1, true); adsp->is_fe_set[sch->active_fe - 1] = false; curr_adma_id = tegra_adsp_get_connected_adma(adsp, sch->active_fe); app_curr = &adsp->apps[curr_adma_id]; apm_msg_curr.msgq_msg.size = MSGQ_MSG_WSIZE(apm_fx_set_param_params_t); apm_msg_curr.msg.call_params.size = sizeof(apm_fx_set_param_params_t); apm_msg_curr.msg.call_params.method = nvfx_apm_method_fx_set_param; apm_msg_curr.msg.fx_set_param_params.plugin.pvoid = app_curr->plugin->plugin.pvoid; num_params = 3; apm_msg_curr.msg.fx_set_param_params.params[0] = (sizeof(nvfx_call_params_t) + num_params * sizeof(int32_t)); /* initialize the method */ apm_msg_curr.msg.fx_set_param_params.params[1] = nvfx_adma_set_null_sink_mode; /* send active switch one to pause */ apm_msg_curr.msg.fx_set_param_params.params[2] = 1; ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync err 0x%x\n", __func__, ret); return ret; } ret = tegra210_adsp_send_msg(app_curr, &apm_msg_curr, TEGRA210_ADSP_MSG_FLAG_SEND | TEGRA210_ADSP_MSG_FLAG_NEED_ACK); pm_runtime_put(adsp->dev); } start_playback: if (fe_reg == 0) { sch->active_fe = fe_reg; return 0; } tegra_ivc_start_playback(adsp, admaif_id - 1, false); app_new = &adsp->apps[conn_adma_id]; apm_msg_new.msgq_msg.size = MSGQ_MSG_WSIZE(apm_fx_set_param_params_t); apm_msg_new.msg.call_params.size = sizeof(apm_fx_set_param_params_t); apm_msg_new.msg.call_params.method = nvfx_apm_method_fx_set_param; apm_msg_new.msg.fx_set_param_params.plugin.pvoid = app_new->plugin->plugin.pvoid; num_params = 3; apm_msg_new.msg.fx_set_param_params.params[0] = (sizeof(nvfx_call_params_t) + num_params * sizeof(int32_t)); /* initialize the method */ apm_msg_new.msg.fx_set_param_params.params[1] = nvfx_adma_set_null_sink_mode; /* send new switch one to unpause */ apm_msg_new.msg.fx_set_param_params.params[2] = 0; ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync error 0x%x\n", __func__, ret); return ret; } ret = tegra210_adsp_send_msg(app_new, &apm_msg_new, TEGRA210_ADSP_MSG_FLAG_SEND | TEGRA210_ADSP_MSG_FLAG_NEED_ACK); pm_runtime_put(adsp->dev); sch->active_fe = fe_reg; adsp->is_fe_set[sch->active_fe - 1] = true; return 0; } static int tegra210_adsp_get_param(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct soc_bytes *params = (void *)kcontrol->private_value; if (params->mask == SNDRV_CTL_ELEM_TYPE_INTEGER) memset(ucontrol->value.integer.value, 0, params->num_regs * sizeof(long)); else memset(ucontrol->value.bytes.data, 0, params->num_regs); return 0; } /* tegra210_adsp_set_param - sets plugin parameters * @default: byte_format * @byte_format: nvfx_call_params_t based structure * @int_format: ,<#params>,,,.... */ static int tegra210_adsp_set_param(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct soc_bytes *params = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_adsp *adsp = snd_soc_component_get_drvdata(cmpnt); struct tegra210_adsp_app *app = &adsp->apps[params->base]; apm_msg_t apm_msg; int ret; if (!adsp->init_done || adsp->is_shutdown) { dev_warn(adsp->dev, "ADSP is not booted yet\n"); return -EPERM; } if (!app->plugin) { dev_warn(adsp->dev, "Unable to set %s, plugin not initialized\n", kcontrol->id.name); return -EPERM; } if (!app->connect) { dev_warn(adsp->dev, "%s, plugin not yet connected\n", kcontrol->id.name); return -EPERM; } 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; switch (params->mask) { case SNDRV_CTL_ELEM_TYPE_INTEGER: { int32_t num_params, i; /* check number of params to pass */ num_params = (int32_t)ucontrol->value.integer.value[1]; if (num_params < 1) { dev_warn(adsp->dev, "No params to pass to the plugin\n"); return 0; } if (num_params + 2 > sizeof(apm_msg.msg.fx_set_param_params.params)/ sizeof(apm_msg.msg.fx_set_param_params.params[0])) { dev_err(adsp->dev, "parameter too large\n"); return -EINVAL; } apm_msg.msg.fx_set_param_params.params[0] = (sizeof(nvfx_call_params_t) + num_params * sizeof(int32_t)); /* initialize the method */ apm_msg.msg.fx_set_param_params.params[1] = (int32_t)ucontrol->value.integer.value[0]; /* copy parameters */ for (i = 0; i < num_params; i++) apm_msg.msg.fx_set_param_params.params[i + 2] = (int32_t)ucontrol->value.integer.value[i + 2]; } break; case SNDRV_CTL_ELEM_TYPE_BYTES: { nvfx_call_params_t *call_params = (nvfx_call_params_t *)ucontrol->value.bytes.data; if (call_params->size > sizeof(apm_msg.msg.fx_set_param_params.params)) { dev_err(adsp->dev, "parameter too large\n"); return -EINVAL; } /* copy parameters */ memcpy(&apm_msg.msg.fx_set_param_params.params, call_params, call_params->size); } break; default: return -EINVAL; } ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync error 0x%x\n", __func__, ret); return ret; } ret = tegra210_adsp_send_msg(app, &apm_msg, TEGRA210_ADSP_MSG_FLAG_SEND | TEGRA210_ADSP_MSG_FLAG_NEED_ACK); pm_runtime_put(adsp->dev); return ret; } static int tegra210_adsp_tlv_callback(struct snd_kcontrol *kcontrol, int op_flag, unsigned int size, unsigned int __user *tlv) { struct soc_bytes *params = (void *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_adsp *adsp = snd_soc_component_get_drvdata(cmpnt); unsigned int count = size < params->num_regs ? size : params->num_regs; struct tegra210_adsp_app *app = &adsp->apps[params->base]; unsigned int *tlv_data; int ret = 0; if (!adsp->init_done || adsp->is_shutdown) { dev_warn(adsp->dev, "ADSP is not booted yet\n"); return 0; } if (!app->plugin) { dev_warn(adsp->dev, "Plugin not yet initialized\n"); return 0; } if (!app->connect) { dev_warn(adsp->dev, "%s, plugin not yet connected\n", kcontrol->id.name); return 0; } tlv_data = devm_kzalloc(adsp->dev, count, GFP_KERNEL); if (!tlv_data) return -ENOMEM; switch (op_flag) { case SNDRV_CTL_TLV_OP_WRITE: { apm_raw_data_msg_t *apm_msg; nvfx_call_params_t *call_params; apm_msg = devm_kzalloc(adsp->dev, sizeof(apm_raw_data_msg_t), GFP_KERNEL); if (!apm_msg) { dev_err(adsp->dev, "Failed to allocate memory for message\n"); ret = -ENOMEM; goto end; } apm_msg->msgq_msg.size = MSGQ_MSG_WSIZE(apm_fx_raw_data_params_t); apm_msg->msg.call_params.size = sizeof(apm_fx_raw_data_params_t); apm_msg->msg.call_params.method = nvfx_apm_method_write_data; apm_msg->msg.fx_raw_data_params.plugin.pvoid = app->plugin->plugin.pvoid; if (copy_from_user(tlv_data, tlv, count)) { ret = -EFAULT; devm_kfree(adsp->dev, apm_msg); goto end; } call_params = (nvfx_call_params_t *)tlv_data; if (call_params->size <= 0 || (call_params->size > sizeof(apm_msg->msg.fx_raw_data_params.data))) { ret = -EINVAL; devm_kfree(adsp->dev, apm_msg); goto end; } memcpy(&apm_msg->msg.fx_raw_data_params.data, call_params, call_params->size); ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync error 0x%x\n", __func__, ret); devm_kfree(adsp->dev, apm_msg); goto end; } ret = tegra210_adsp_send_raw_data_msg(app, apm_msg); pm_runtime_put(adsp->dev); devm_kfree(adsp->dev, apm_msg); break; } case SNDRV_CTL_TLV_OP_READ: { int src = app->reg; struct tegra210_adsp_app *apm; while (!IS_APM_IN(src)) src = tegra210_adsp_get_source(adsp, src); apm = &adsp->apps[src]; ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync error 0x%x\n", __func__, ret); goto end; } ret = tegra210_adsp_send_data_request_msg(app, count, TEGRA210_ADSP_MSG_FLAG_SEND); if (ret < 0) { pm_runtime_put(adsp->dev); dev_err(adsp->dev, "Raw read request dropped\n"); ret = -ETIMEDOUT; goto end; } ret = wait_for_completion_interruptible_timeout( apm->raw_msg_read_complete, msecs_to_jiffies(ADSP_RESPONSE_TIMEOUT)); pm_runtime_put(adsp->dev); if (ret <= 0) { dev_err(adsp->dev, "No data received\n"); ret = -ETIMEDOUT; goto end; } if (copy_to_user(tlv, apm->read_data.data, count)) { ret = -EFAULT; goto end; } } } end: devm_kfree(adsp->dev, tlv_data); return ret; } static int tegra210_adsp_apm_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_adsp *adsp = snd_soc_component_get_drvdata(cmpnt); struct tegra210_adsp_app *app = &adsp->apps[mc->reg]; if (strstr(kcontrol->id.name, "Priority")) { ucontrol->value.integer.value[0] = app->priority; } else if (strstr(kcontrol->id.name, "Min ADSP Clock")) { ucontrol->value.integer.value[0] = app->min_adsp_clock; } else if (strstr(kcontrol->id.name, "Input Mode")) { ucontrol->value.integer.value[0] = app->input_mode; } return 0; } static int tegra210_adsp_apm_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); struct tegra210_adsp *adsp = snd_soc_component_get_drvdata(cmpnt); struct tegra210_adsp_app *app = &adsp->apps[mc->reg]; apm_msg_t apm_msg; bool send_msg = 0; int ret = 0; if (!adsp->init_done || adsp->is_shutdown) { dev_warn(adsp->dev, "ADSP is not booted yet\n"); return -EPERM; } /* Controls here may execute whether or not APM is initialized */ if (strstr(kcontrol->id.name, "Min ADSP Clock")) { app->min_adsp_clock = ucontrol->value.integer.value[0]; return 0; } /* Check for APM initialized */ if (!app->plugin) { dev_warn(adsp->dev, "Unable to set %s, APM %d not initialized\n", kcontrol->id.name, mc->reg); return -EPERM; } if (strstr(kcontrol->id.name, "Priority")) { apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(apm_set_priority_params_t); apm_msg.msg.call_params.size = sizeof(apm_set_priority_params_t); apm_msg.msg.call_params.method = nvfx_apm_method_set_priority; apm_msg.msg.priority_params.priority = ucontrol->value.integer.value[0]; app->priority = ucontrol->value.integer.value[0]; send_msg = true; } else if (strstr(kcontrol->id.name, "Input Mode")) { apm_msg.msgq_msg.size = MSGQ_MSG_WSIZE(apm_set_input_mode_params_t); apm_msg.msg.call_params.size = sizeof(apm_set_input_mode_params_t); apm_msg.msg.call_params.method = nvfx_apm_method_set_input_mode; apm_msg.msg.input_mode_params.mode = ucontrol->value.integer.value[0]; app->input_mode = ucontrol->value.integer.value[0]; send_msg = true; } if (send_msg) { ret = pm_runtime_get_sync(adsp->dev); if (ret < 0) { dev_err(adsp->dev, "%s pm_runtime_get_sync error 0x%x\n", __func__, ret); return ret; } ret = tegra210_adsp_send_msg(app, &apm_msg, TEGRA210_ADSP_MSG_FLAG_SEND | TEGRA210_ADSP_MSG_FLAG_NEED_ACK); pm_runtime_put(adsp->dev); } return ret; } /* Maximum 128 integers or 512 bytes allowed */ #define SND_SOC_PARAM_EXT(xname, xbase) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .name = xname, \ .info = tegra210_adsp_param_info, \ .get = tegra210_adsp_get_param, \ .put = tegra210_adsp_set_param, \ .private_value = \ ((unsigned long)&(struct soc_bytes) \ {.base = xbase, .num_regs = 512, \ .mask = SNDRV_CTL_ELEM_TYPE_BYTES}) } #define SND_SOC_PARAM_TLV(xname, xbase, xcount) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .name = xname, \ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE | \ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ .tlv.c = tegra210_adsp_tlv_callback, \ .info = snd_soc_bytes_info_ext, \ .private_value = \ ((unsigned long)&(struct soc_bytes) \ {.base = xbase, .num_regs = xcount, \ .mask = SNDRV_CTL_ELEM_TYPE_BYTES}) } #define APM_CONTROL(xname, xmax) \ SOC_SINGLE_EXT("APM1 " xname, TEGRA210_ADSP_APM_IN1, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM2 " xname, TEGRA210_ADSP_APM_IN2, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM3 " xname, TEGRA210_ADSP_APM_IN3, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM4 " xname, TEGRA210_ADSP_APM_IN4, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM5 " xname, TEGRA210_ADSP_APM_IN5, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM6 " xname, TEGRA210_ADSP_APM_IN6, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM7 " xname, TEGRA210_ADSP_APM_IN7, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM8 " xname, TEGRA210_ADSP_APM_IN8, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM9 " xname, TEGRA210_ADSP_APM_IN9, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM10 " xname, TEGRA210_ADSP_APM_IN10, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM11 " xname, TEGRA210_ADSP_APM_IN11, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM12 " xname, TEGRA210_ADSP_APM_IN12, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM13 " xname, TEGRA210_ADSP_APM_IN13, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM14 " xname, TEGRA210_ADSP_APM_IN14, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put), \ SOC_SINGLE_EXT("APM15 " xname, TEGRA210_ADSP_APM_IN15, 0, xmax, 0,\ tegra210_adsp_apm_get, tegra210_adsp_apm_put) static const struct soc_enum tegra210_adsp_switch1 = SOC_ENUM_SINGLE(1/* corresponds to admaif id */, 0, ADSP_FE_END + 1, tegra210_adsp_mux_texts); static const struct soc_enum tegra210_adsp_switch2 = SOC_ENUM_SINGLE(2/* corresponds to admaif id */, 0, ADSP_FE_END + 1, tegra210_adsp_mux_texts); static const struct soc_enum tegra210_adsp_switch3 = SOC_ENUM_SINGLE(3/* corresponds to admaif id */, 0, ADSP_FE_END + 1, tegra210_adsp_mux_texts); #define SWITCH_CONTROL(xname, xreg) \ SOC_ENUM_EXT(xname, tegra210_adsp_switch##xreg, \ tegra210_adsp_get_switch, tegra210_adsp_set_switch) /* Any new addition of control should be added after PLUGIN controls otherwise * the index of PLUGIN needs to be changed with define PLUGIN_SET_PARAMS_IDX and * PLUGIN_SEND_BYTES_IDX */ static struct snd_kcontrol_new tegra210_adsp_controls[] = { SOC_SINGLE_BOOL_EXT("ADSP init", 0, tegra210_adsp_init_get, tegra210_adsp_init_put), SND_SOC_PARAM_EXT("PLUGIN1-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN1), SND_SOC_PARAM_EXT("PLUGIN2-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN2), SND_SOC_PARAM_EXT("PLUGIN3-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN3), SND_SOC_PARAM_EXT("PLUGIN4-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN4), SND_SOC_PARAM_EXT("PLUGIN5-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN5), SND_SOC_PARAM_EXT("PLUGIN6-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN6), SND_SOC_PARAM_EXT("PLUGIN7-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN7), SND_SOC_PARAM_EXT("PLUGIN8-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN8), SND_SOC_PARAM_EXT("PLUGIN9-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN9), SND_SOC_PARAM_EXT("PLUGIN10-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN10), SND_SOC_PARAM_EXT("PLUGIN11-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN11), SND_SOC_PARAM_EXT("PLUGIN12-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN12), SND_SOC_PARAM_EXT("PLUGIN13-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN13), SND_SOC_PARAM_EXT("PLUGIN14-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN14), SND_SOC_PARAM_EXT("PLUGIN15-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN15), SND_SOC_PARAM_EXT("PLUGIN16-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN16), SND_SOC_PARAM_EXT("PLUGIN17-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN17), SND_SOC_PARAM_EXT("PLUGIN18-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN18), SND_SOC_PARAM_EXT("PLUGIN19-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN19), SND_SOC_PARAM_EXT("PLUGIN20-PLACE-HOLDER set params", TEGRA210_ADSP_PLUGIN20), SND_SOC_PARAM_TLV("PLUGIN1-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN1, 0x1000), SND_SOC_PARAM_TLV("PLUGIN2-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN2, 0x1000), SND_SOC_PARAM_TLV("PLUGIN3-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN3, 0x1000), SND_SOC_PARAM_TLV("PLUGIN4-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN4, 0x1000), SND_SOC_PARAM_TLV("PLUGIN5-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN5, 0x1000), SND_SOC_PARAM_TLV("PLUGIN6-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN6, 0x1000), SND_SOC_PARAM_TLV("PLUGIN7-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN7, 0x1000), SND_SOC_PARAM_TLV("PLUGIN8-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN8, 0x1000), SND_SOC_PARAM_TLV("PLUGIN9-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN9, 0x1000), SND_SOC_PARAM_TLV("PLUGIN10-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN10, 0x1000), SND_SOC_PARAM_TLV("PLUGIN11-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN11, 0x1000), SND_SOC_PARAM_TLV("PLUGIN12-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN12, 0x1000), SND_SOC_PARAM_TLV("PLUGIN13-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN13, 0x1000), SND_SOC_PARAM_TLV("PLUGIN14-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN14, 0x1000), SND_SOC_PARAM_TLV("PLUGIN15-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN15, 0x1000), SND_SOC_PARAM_TLV("PLUGIN16-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN16, 0x1000), SND_SOC_PARAM_TLV("PLUGIN17-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN17, 0x1000), SND_SOC_PARAM_TLV("PLUGIN18-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN18, 0x1000), SND_SOC_PARAM_TLV("PLUGIN19-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN19, 0x1000), SND_SOC_PARAM_TLV("PLUGIN20-PLACE-HOLDER send bytes", TEGRA210_ADSP_PLUGIN20, 0x1000), SND_SOC_PARAM_EXT("ADMA1 set params", TEGRA210_ADSP_PLUGIN_ADMA1), SND_SOC_PARAM_EXT("ADMA2 set params", TEGRA210_ADSP_PLUGIN_ADMA2), SND_SOC_PARAM_EXT("ADMA3 set params", TEGRA210_ADSP_PLUGIN_ADMA3), SND_SOC_PARAM_EXT("ADMA4 set params", TEGRA210_ADSP_PLUGIN_ADMA4), SND_SOC_PARAM_EXT("ADMA5 set params", TEGRA210_ADSP_PLUGIN_ADMA5), SND_SOC_PARAM_EXT("ADMA6 set params", TEGRA210_ADSP_PLUGIN_ADMA6), SND_SOC_PARAM_EXT("ADMA7 set params", TEGRA210_ADSP_PLUGIN_ADMA7), SND_SOC_PARAM_EXT("ADMA8 set params", TEGRA210_ADSP_PLUGIN_ADMA8), SND_SOC_PARAM_EXT("ADMA9 set params", TEGRA210_ADSP_PLUGIN_ADMA9), SND_SOC_PARAM_EXT("ADMA10 set params", TEGRA210_ADSP_PLUGIN_ADMA10), SND_SOC_PARAM_EXT("ADMA11 set params", TEGRA210_ADSP_PLUGIN_ADMA11), SND_SOC_PARAM_EXT("ADMA12 set params", TEGRA210_ADSP_PLUGIN_ADMA12), SND_SOC_PARAM_EXT("ADMA13 set params", TEGRA210_ADSP_PLUGIN_ADMA13), SND_SOC_PARAM_EXT("ADMA14 set params", TEGRA210_ADSP_PLUGIN_ADMA14), SND_SOC_PARAM_EXT("ADMA15 set params", TEGRA210_ADSP_PLUGIN_ADMA15), SND_SOC_PARAM_EXT("ADMA1-TX set params", TEGRA210_ADSP_PLUGIN_ADMA1_TX), SND_SOC_PARAM_EXT("ADMA2-TX set params", TEGRA210_ADSP_PLUGIN_ADMA2_TX), SND_SOC_PARAM_EXT("ADMA3-TX set params", TEGRA210_ADSP_PLUGIN_ADMA3_TX), SND_SOC_PARAM_EXT("ADMA4-TX set params", TEGRA210_ADSP_PLUGIN_ADMA4_TX), SND_SOC_PARAM_EXT("ADMA5-TX set params", TEGRA210_ADSP_PLUGIN_ADMA5_TX), SND_SOC_PARAM_EXT("ADMA6-TX set params", TEGRA210_ADSP_PLUGIN_ADMA6_TX), SND_SOC_PARAM_EXT("ADMA7-TX set params", TEGRA210_ADSP_PLUGIN_ADMA7_TX), SND_SOC_PARAM_EXT("ADMA8-TX set params", TEGRA210_ADSP_PLUGIN_ADMA8_TX), SND_SOC_PARAM_EXT("ADMA9-TX set params", TEGRA210_ADSP_PLUGIN_ADMA9_TX), SND_SOC_PARAM_EXT("ADMA10-TX set params", TEGRA210_ADSP_PLUGIN_ADMA10_TX), SND_SOC_PARAM_EXT("ADMA11-TX set params", TEGRA210_ADSP_PLUGIN_ADMA11_TX), SND_SOC_PARAM_EXT("ADMA12-TX set params", TEGRA210_ADSP_PLUGIN_ADMA12_TX), SND_SOC_PARAM_EXT("ADMA13-TX set params", TEGRA210_ADSP_PLUGIN_ADMA13_TX), SND_SOC_PARAM_EXT("ADMA14-TX set params", TEGRA210_ADSP_PLUGIN_ADMA14_TX), SND_SOC_PARAM_EXT("ADMA15-TX set params", TEGRA210_ADSP_PLUGIN_ADMA15_TX), APM_CONTROL("Priority", APM_PRIORITY_MAX), APM_CONTROL("Min ADSP Clock", INT_MAX), APM_CONTROL("Input Mode", INT_MAX), SWITCH_CONTROL("MS_ENT", 1), SWITCH_CONTROL("MS_PH_PHONE", 2), SWITCH_CONTROL("MS_ANN", 3), #ifdef CONFIG_TEGRA_ADSP_LPTHREAD SOC_SINGLE_BOOL_EXT("lpthread init", 0, tegra210_adsp_lpthread_init_get, tegra210_adsp_lpthread_init_put), #endif }; static int tegra210_adsp_component_probe(struct snd_soc_component *component) { component->read = tegra210_adsp_read; component->write = tegra210_adsp_write; return 0; } static const struct snd_soc_component_driver tegra210_adsp_component = { .name = "tegra210-adsp", .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), .probe = tegra210_adsp_component_probe, }; 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 int tegra210_adsp_pcm_probe(struct snd_soc_platform *platform) { struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(&platform->component); dapm->idle_bias_off = 1; platform->component.read = tegra210_adsp_read; platform->component.write = tegra210_adsp_write; return 0; } 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, }; static u64 tegra_dma_mask = DMA_BIT_MASK(32); static struct adsp_soc_data adsp_soc_data_t186 = { .is_soc_t210 = false, .max_adma_ch = TEGRA186_MAX_ADMA_CHANNEL, }; static const struct of_device_id tegra210_adsp_audio_of_match[] = { { .compatible = "nvidia,tegra210-adsp-audio-hv", .data = &adsp_soc_data_t186}, {}, }; static void adsp_control_name_override(struct device *dev, int wt_idx, int i, int mux_idx) { char *name = devm_kzalloc(dev, strlen(adsp_app_desc[i].wt_name) + 3, GFP_KERNEL); strcpy((char *)name, adsp_app_desc[i].wt_name); strcat((char *)name, " TX"); tegra210_adsp_widgets[wt_idx].name = name; name = devm_kzalloc(dev, strlen(adsp_app_desc[i].wt_name) + 4, GFP_KERNEL); strcpy((char *)name, adsp_app_desc[i].wt_name); strcat((char *)name, " MUX"); tegra210_adsp_widgets[wt_idx+PLUGIN_SET_PARAMS_IDX].name = name; name = devm_kzalloc(dev, strlen(adsp_app_desc[i].wt_name) + 12, GFP_KERNEL); strcpy((char *)name, adsp_app_desc[i].wt_name); strcat((char *)name, " set params"); tegra210_adsp_controls[i+PLUGIN_SET_PARAMS_IDX].name = name; name = devm_kzalloc(dev, strlen(adsp_app_desc[i].wt_name) + 12, GFP_KERNEL); strcpy((char *)name, adsp_app_desc[i].wt_name); strcat((char *)name, " send bytes"); tegra210_adsp_controls[i+PLUGIN_SEND_BYTES_IDX].name = name; name = devm_kzalloc(dev, strlen(adsp_app_desc[i].wt_name) + 1, GFP_KERNEL); strcpy((char *)name, adsp_app_desc[i].wt_name); tegra210_adsp_mux_texts[mux_idx] = name; } static int tegra210_adsp_audio_platform_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node, *subnp; const struct of_device_id *match; struct soc_bytes *controls; struct tegra210_adsp *adsp; int i, j, wt_idx, mux_idx, ret = 0; unsigned int compr_ops = 1; uint32_t adma_ch_page = 0; uint32_t adma_ch_start = TEGRA210_ADSP_ADMA_CHANNEL_START_HV; uint32_t adma_ch_cnt = TEGRA210_ADSP_ADMA_CHANNEL_COUNT; char plugin_info[20], apm_info[20]; char switch_info[20]; uint32_t adsp_switch_count; struct netlink_kernel_cfg cfg = { .input = tegra210_adsp_nl_recv_msg, }; pr_info("tegra210_adsp_audio_platform_probe: platform probe started\n"); if (dev_set_name(&pdev->dev, "%s", DRV_NAME_ADSP) < 0) { dev_err(&pdev->dev, "error in setting adsp device name\n"); return -ENODEV; } match = of_match_device(tegra210_adsp_audio_of_match, &pdev->dev); if (!match) { dev_err(&pdev->dev, "Error: No device match found\n"); return -ENODEV; } adsp = devm_kzalloc(&pdev->dev, sizeof(*adsp), GFP_KERNEL); if (!adsp) { dev_err(&pdev->dev, "Can't allocate tegra210_adsp_ctx\n"); return -ENOMEM; } dev_set_drvdata(&pdev->dev, adsp); adsp->dev = &pdev->dev; adsp->is_shutdown = false; adsp->soc_data = (struct adsp_soc_data *)match->data; if (!(tegra_platform_is_unit_fpga() || tegra_platform_is_fpga())) { adsp->ahub_clk = devm_clk_get(&pdev->dev, "ahub"); if (IS_ERR(adsp->ahub_clk)) { dev_err(&pdev->dev, "Error: Missing AHUB clock\n"); ret = PTR_ERR(adsp->ahub_clk); goto err; } adsp->ape_clk = devm_clk_get(&pdev->dev, "ape"); if (IS_ERR(adsp->ape_clk)) { dev_err(&pdev->dev, "Error: Missing APE clock\n"); ret = PTR_ERR(adsp->ape_clk); goto err; } if (!adsp->soc_data->is_soc_t210) { adsp->apb2ape_clk = devm_clk_get(&pdev->dev, "apb2ape"); if (IS_ERR(adsp->apb2ape_clk)) { dev_err(&pdev->dev, "Error: Missing APB2APE clock\n"); ret = PTR_ERR(adsp->apb2ape_clk); goto err; } } } /* TODO: Add mixer control to set I2S playback rate */ adsp->i2s_rate = 48000; INIT_WORK(&adsp->override_freq_work, tegra_adsp_override_freq_worker); mutex_init(&adsp->mutex); pdev->dev.dma_mask = &tegra_dma_mask; pdev->dev.coherent_dma_mask = tegra_dma_mask; tegra_pd_add_device(&pdev->dev); pm_runtime_enable(&pdev->dev); if (!pm_runtime_enabled(&pdev->dev)) goto err_pm_disable; for (i = 0; i < TEGRA210_ADSP_VIRT_REG_MAX; i++) { adsp->apps[i].reg = i; adsp->apps[i].priority = 0; adsp->apps[i].min_adsp_clock = 0; adsp->apps[i].input_mode = NVFX_APM_INPUT_MODE_PUSH; } ret = device_property_read_string_array(&pdev->dev, "fe-info", NULL, 0); if (ret > 0) adsp_fe_name_override(&pdev->dev, ret); /* get the plugin count */ if (of_property_read_u32(pdev->dev.of_node, "num-plugin", &adsp_app_count) < 0) { dev_warn(&pdev->dev, "Missing ADSP plugin count\n"); adsp_app_count = 0; } /* check for maximum number of plugins */ if (adsp_app_count > PLUGIN_NUM) { dev_err(&pdev->dev, "num-plugin: %d\n", adsp_app_count); dev_err(&pdev->dev, "Max plugins supported: %d\n", PLUGIN_NUM); adsp_app_count = PLUGIN_NUM; } /* allocate memory for app descritors */ adsp_app_desc = devm_kzalloc(&pdev->dev, (adsp_app_count + ARRAY_SIZE(adsp_app_minimal)) * sizeof(*adsp_app_desc), GFP_KERNEL); if (!adsp_app_desc) { dev_err(&pdev->dev, "Can't allocate tegra210_adsp_app descriptor\n"); ret = -ENOMEM; goto err_pm_disable; } /* parse the plugin, firmware, widget names and params */ for (i = 0; i < adsp_app_count; i++) { memset((void *)plugin_info, '\0', 20); sprintf(plugin_info, "plugin-info-%d", i+1); subnp = of_get_child_by_name(np, plugin_info); if (subnp) { if (of_property_read_string(subnp, "plugin-name", &adsp_app_desc[i].name)) { dev_err(&pdev->dev, "Missing property plugin-name\n"); ret = -EINVAL; goto err_pm_disable; } if (of_property_read_string(subnp, "firmware-name", &adsp_app_desc[i].fw_name)) { dev_err(&pdev->dev, "Missing property firmware-name\n"); ret = -EINVAL; goto err_pm_disable; } if (of_property_read_string(subnp, "widget-name", &adsp_app_desc[i].wt_name)) { dev_warn(&pdev->dev, "Missing property widget-name for %s\n", adsp_app_desc[i].name); adsp_app_desc[i].wt_name = NULL; } else { if (adsp_app_desc[i].wt_name) { /* override the widget names from DT if any */ mux_idx = TEGRA210_ADSP_PLUGIN1 + i; wt_idx = TEGRA210_ADSP_WIDGET_BASE + (2*i); if (strlen(tegra210_adsp_mux_texts[mux_idx]) < strlen(adsp_app_desc[i].wt_name)) { dev_err(&pdev->dev, "Widget name too long %s, allowed len %zu\n", adsp_app_desc[i].wt_name, strlen(tegra210_adsp_mux_texts[mux_idx])); continue; } tegra210_adsp_route_modify(&pdev->dev, tegra210_adsp_mux_texts[mux_idx], adsp_app_desc[i].wt_name); adsp_control_name_override(&pdev->dev, wt_idx, i, mux_idx); } } if (of_property_read_u32(subnp, "param-type", &adsp_app_desc[i].param_type)) { dev_info(&pdev->dev, "Default param-type to BYTE for %s\n", adsp_app_desc[i].name); adsp_app_desc[i].param_type = SNDRV_CTL_ELEM_TYPE_BYTES; } else { /* override the param-type from DT if any */ controls = (void *)tegra210_adsp_controls[i+1].private_value; controls->mask = adsp_app_desc[i].param_type; } adsp_app_desc[i].reg_start = TEGRA210_ADSP_PLUGIN1 + i; adsp_app_desc[i].reg_end = TEGRA210_ADSP_PLUGIN1 + i; } else { dev_err(&pdev->dev, "Property '%s' missing or invalid\n", plugin_info); ret = -EINVAL; goto err_pm_disable; } } /* copy basic apps needed */ memcpy(&adsp_app_desc[adsp_app_count], &adsp_app_minimal[0], sizeof(adsp_app_minimal)); adsp_app_count += ARRAY_SIZE(adsp_app_minimal); for (i = 0; i < adsp_app_count; 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]; } /* enable/disable compr-ops from DT */ of_property_read_u32(pdev->dev.of_node, "compr-ops", &compr_ops); if (!compr_ops) tegra210_adsp_platform.compr_ops = NULL; if (of_property_read_u32_index(pdev->dev.of_node, "nvidia,adma_ch_page", 0, &adma_ch_page)) { dev_info(&pdev->dev, "adma channel page address dt entry not found\n"); dev_info(&pdev->dev, "using adma channel page 0\n"); } adsp->adma_ch_page = adma_ch_page; adma_ch_start = TEGRA210_ADSP_ADMA_CHANNEL_START_HV; if (of_property_read_u32_index(pdev->dev.of_node, "nvidia,adma_ch_start", 0, &adma_ch_start)) { dev_info(&pdev->dev, "adma channel start dt entry not found\n"); dev_info(&pdev->dev, "using adma channels from 16\n"); } adsp->adma_ch_start = (adma_ch_start > adsp->soc_data->max_adma_ch) ? adsp->soc_data->max_adma_ch : adma_ch_start; if (of_property_read_u32_index(pdev->dev.of_node, "nvidia,adma_ch_cnt", 0, &adma_ch_cnt)) { dev_info(&pdev->dev, "adma channel cnt dt entry not found\n"); dev_info(&pdev->dev, "using default adma channels count 10\n"); } adsp->adma_ch_cnt = adma_ch_cnt; if ((adsp->adma_ch_start + adsp->adma_ch_cnt) > adsp->soc_data->max_adma_ch) { adsp->adma_ch_cnt = adsp->soc_data->max_adma_ch - adsp->adma_ch_start; dev_info(&pdev->dev, "adma channel cnt set to %d\n", adsp->adma_ch_cnt); } 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_component; } spin_lock_init(&adsp->switch_lock); /* get switch count */ if (of_property_read_u32(pdev->dev.of_node, "num-switch", &adsp_switch_count) < 0) { dev_info(&pdev->dev, "Missing ADSP switch count\n"); adsp_switch_count = 0; } adsp_switch_count = adsp_switch_count > MAX_ADSP_SWITCHES ? MAX_ADSP_SWITCHES : adsp_switch_count; for (i = 0; i < adsp_switch_count; i++) { memset((void *)switch_info, '\0', 20); sprintf(switch_info, "switch-info-%d", i+1); subnp = of_get_child_by_name(np, switch_info); if (subnp) { if (of_property_read_u32(subnp, "allowed-fe", &adsp->switches[i].allowed_fe)) { dev_err(&pdev->dev, "Missing property allowed-fe\n"); adsp->switches[i].allowed_fe = 0x0; } if (of_property_read_u32(subnp, "admaif-id", &adsp->switches[i].admaif_id)) { dev_err(&pdev->dev, "Missing property admaif-id\n"); adsp->switches[i].admaif_id = 0x1; } } } for (i = 0; i < (APM_IN_END - APM_IN_START + 1); i++) { apm_stack_size[i] = 0; memset((void *)apm_info, '\0', 20); sprintf(apm_info, "apm%d-stack-size", i+1); of_property_read_u32(pdev->dev.of_node, apm_info, &apm_stack_size[i]); } adsp->nl_sk = netlink_kernel_create(&init_net, NETLINK_ADSP_EVENT, &cfg); if (!adsp->nl_sk) { dev_err(&pdev->dev, "Error creating socket\n"); ret = -ENOMEM; goto err_unregister_codec; } dev_info(&pdev->dev, "Tegra210 ADSP driver successfully registered\n"); return 0; err_unregister_codec: snd_soc_unregister_codec(&pdev->dev); err_unregister_component: snd_soc_unregister_component(&pdev->dev); err_unregister_platform: snd_soc_unregister_platform(&pdev->dev); err_pm_disable: pm_runtime_disable(&pdev->dev); err: return ret; } static int tegra210_adsp_audio_platform_remove(struct platform_device *pdev) { struct tegra210_adsp *adsp = dev_get_drvdata(&pdev->dev); netlink_kernel_release(adsp->nl_sk); snd_soc_unregister_codec(&pdev->dev); snd_soc_unregister_component(&pdev->dev); pm_runtime_disable(&pdev->dev); tegra_pd_remove_device(&pdev->dev); snd_soc_unregister_platform(&pdev->dev); return 0; } static void tegra210_adsp_audio_platform_shutdown( struct platform_device *pdev) { struct tegra210_adsp *adsp = dev_get_drvdata(&pdev->dev); tegra210_adsp_deinit(adsp); adsp->is_shutdown = true; } #ifdef CONFIG_PM_SLEEP static int tegra_adsp_pm_suspend(struct device *dev) { return pm_runtime_force_suspend(dev); } static int tegra_adsp_pm_resume(struct device *dev) { return pm_runtime_force_resume(dev); } #endif static const struct dev_pm_ops tegra210_adsp_pm_ops = { SET_RUNTIME_PM_OPS(tegra210_adsp_runtime_suspend, tegra210_adsp_runtime_resume, NULL) SET_LATE_SYSTEM_SLEEP_PM_OPS(tegra_adsp_pm_suspend, tegra_adsp_pm_resume) }; static struct platform_driver tegra210_adsp_audio_driver = { .driver = { .name = DRV_NAME_ADSP, .owner = THIS_MODULE, .of_match_table = tegra210_adsp_audio_of_match, .pm = &tegra210_adsp_pm_ops, .suppress_bind_attrs = true, }, .probe = tegra210_adsp_audio_platform_probe, .shutdown = tegra210_adsp_audio_platform_shutdown, .remove = tegra210_adsp_audio_platform_remove, }; module_platform_driver(tegra210_adsp_audio_driver); MODULE_AUTHOR("Sumit Bhattacharya "); MODULE_DESCRIPTION("Tegra210 ADSP Audio driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:" DRV_NAME_ADSP); MODULE_DEVICE_TABLE(of, tegra210_adsp_audio_of_match);