ASoC: qcom: enable audio on stage-2 protected DSPs (mDSP)

Ajay Kumar Nandam <ajay.nandam@oss.qualcomm.com> says:

On platforms such as Qualcomm Shikra, audio is served by the modem DSP
(mDSP) rather than the ADSP. The mDSP runs in a stage-2 protected context
and cannot use the SMMU, so the PCM buffers it consumes must live in
reserved-memory carveouts that are handed to the consumer VMIDs via a
hypervisor (SCM) memory assignment. This series adds that access model to
the q6apm DAI driver and its binding, alongside the existing stage-1/SMMU
(iommus) path, which is left untouched.

The driver detects the mDSP target at runtime from the GPR domain_id
(GPR_DOMAIN_ID_MODEM) and enables the SCM assignment path automatically.
VMIDs are hardcoded in the driver (HLOS + MSS_MSA + LPASS) rather than
read from DT, following the consensus from v2 review discussion with
Krzysztof Kozlowski and Srinivas Kandagatla.

Tested on Qualcomm Shikra with mDSP audio playback and capture.

Link: https://patch.msgid.link/20260923-vmid-v4-v6-0-3d61a0319b1c@oss.qualcomm.com
This commit is contained in:
Mark Brown 2026-10-06 17:37:53 +02:00
commit 76f97899ec
8 changed files with 348 additions and 34 deletions

View File

@ -20,9 +20,24 @@ properties:
minItems: 1
maxItems: 2
memory-region:
minItems: 1
items:
- description:
Control-path buffer (audio_heap_mem) for platforms where the DSP
runs in a stage-2 protected context (e.g. modem-DSP).
- description:
Data-path buffer pool from which per-stream DMA buffers are
allocated.
required:
- compatible
- iommus
oneOf:
- required:
- iommus
- required:
- memory-region
additionalProperties: false

View File

@ -102,6 +102,7 @@ config SND_SOC_QDSP6_ASM_DAI
config SND_SOC_QDSP6_APM_DAI
tristate
select SND_SOC_COMPRESS
select QCOM_SCM
config SND_SOC_QDSP6_APM_LPASS_DAI
tristate

View File

@ -593,10 +593,10 @@ EXPORT_SYMBOL_GPL(audioreach_alloc_graph_pkt);
int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev,
struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock,
gpr_port_t *port, wait_queue_head_t *cmd_wait,
const struct gpr_pkt *pkt, uint32_t rsp_opcode)
struct gpr_pkt *pkt, uint32_t rsp_opcode)
{
const struct gpr_hdr *hdr = &pkt->hdr;
struct gpr_hdr *hdr = &pkt->hdr;
int rc;
mutex_lock(cmd_lock);
@ -636,10 +636,12 @@ int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev,
}
EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync);
int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt,
int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, struct gpr_pkt *pkt,
uint32_t rsp_opcode)
{
pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev);
return audioreach_send_cmd_sync(graph->dev, NULL, &graph->result, &graph->lock,
graph->port, &graph->cmd_wait, pkt, rsp_opcode);
}
@ -994,6 +996,8 @@ int audioreach_compr_set_param(struct q6apm_graph *graph,
if (rc)
return rc;
pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev);
return gpr_send_port_pkt(graph->port, pkt);
}
EXPORT_SYMBOL_GPL(audioreach_compr_set_param);
@ -1609,6 +1613,8 @@ int audioreach_shared_memory_send_eos(struct q6apm_graph *graph)
eos->policy = WR_SH_MEM_EP_EOS_POLICY_LAST;
pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev);
return gpr_send_port_pkt(graph->port, pkt);
}
EXPORT_SYMBOL_GPL(audioreach_shared_memory_send_eos);

View File

@ -1002,14 +1002,19 @@ struct audioreach_module_config {
};
/* Packet Allocation routines */
void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, uint32_t
token);
static inline u16 audioreach_gpr_dest_domain(gpr_device_t *gdev)
{
return gdev && gdev->domain_id ? gdev->domain_id : GPR_DOMAIN_ID_ADSP;
}
void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode,
uint32_t token);
void audioreach_set_default_channel_mapping(u8 *ch_map, int num_channels);
void *audioreach_alloc_cmd_pkt(int payload_size, uint32_t opcode,
uint32_t token, uint32_t src_port,
uint32_t dest_port);
void *audioreach_alloc_apm_pkt(int pkt_size, uint32_t opcode, uint32_t token,
uint32_t src_port);
uint32_t src_port);
void *audioreach_alloc_pkt(int payload_size, uint32_t opcode,
uint32_t token, uint32_t src_port,
uint32_t dest_port);
@ -1020,10 +1025,13 @@ int audioreach_tplg_init(struct snd_soc_component *component);
/* Module specific */
void audioreach_graph_free_buf(struct q6apm_graph *graph);
int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result,
struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait,
const struct gpr_pkt *pkt, uint32_t rsp_opcode);
int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt,
int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev,
struct gpr_ibasic_rsp_result_t *result,
struct mutex *cmd_lock, gpr_port_t *port,
wait_queue_head_t *cmd_wait,
struct gpr_pkt *pkt, uint32_t rsp_opcode);
int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph,
struct gpr_pkt *pkt,
uint32_t rsp_opcode);
int audioreach_set_media_format(struct q6apm_graph *graph,
const struct audioreach_module *module,

View File

@ -1,20 +1,25 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2021, Linaro Limited
#include <linux/init.h>
#include <dt-bindings/firmware/qcom,scm.h>
#include <dt-bindings/soc/qcom,gpr.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/firmware/qcom/qcom_scm.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <linux/spinlock.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <asm/div64.h>
#include <asm/dma.h>
#include <linux/dma-mapping.h>
#include <sound/pcm_params.h>
#include "q6apm.h"
#define DRV_NAME "q6apm-dai"
@ -36,6 +41,16 @@
#define COMPR_PLAYBACK_MIN_NUM_FRAGMENTS (4)
#define SID_MASK_DEFAULT 0xF
#define Q6APM_MAX_SCM_REGIONS 16
#define Q6APM_POOL_MAX_STREAMS 8
struct q6apm_scm_region {
phys_addr_t addr;
size_t size;
u64 src_perms;
bool assigned;
};
static const struct snd_compr_codec_caps q6apm_compr_caps = {
.num_descriptors = 1,
.descriptor[0].max_ch = 2,
@ -84,9 +99,103 @@ struct q6apm_dai_rtd {
};
struct q6apm_dai_data {
struct device *dev;
long long sid;
bool use_scm_assign;
bool has_reserved_mem;
size_t reserved_buf_size;
struct mutex scm_lock; /* protects scm_regions and num_scm_regions */
struct q6apm_scm_region scm_regions[Q6APM_MAX_SCM_REGIONS];
int num_scm_regions;
};
static int q6apm_dai_scm_assign(struct q6apm_dai_data *pdata,
phys_addr_t addr, size_t size)
{
struct qcom_scm_vmperm dst[] = {
{ .vmid = QCOM_SCM_VMID_HLOS, .perm = QCOM_SCM_PERM_RW },
{ .vmid = QCOM_SCM_VMID_MSS_MSA, .perm = QCOM_SCM_PERM_RW },
{ .vmid = QCOM_SCM_VMID_LPASS, .perm = QCOM_SCM_PERM_RW },
};
struct q6apm_scm_region *r;
u64 src = BIT(QCOM_SCM_VMID_HLOS);
int ret;
mutex_lock(&pdata->scm_lock);
if (pdata->num_scm_regions >= Q6APM_MAX_SCM_REGIONS) {
mutex_unlock(&pdata->scm_lock);
return -ENOSPC;
}
ret = qcom_scm_assign_mem(addr, size, &src, dst, ARRAY_SIZE(dst));
if (ret) {
mutex_unlock(&pdata->scm_lock);
return ret;
}
r = &pdata->scm_regions[pdata->num_scm_regions++];
r->addr = addr;
r->size = size;
r->src_perms = src;
r->assigned = true;
mutex_unlock(&pdata->scm_lock);
return 0;
}
static void q6apm_dai_scm_unassign(struct q6apm_dai_data *pdata,
phys_addr_t addr)
{
struct qcom_scm_vmperm hlos = {
.vmid = QCOM_SCM_VMID_HLOS,
.perm = QCOM_SCM_PERM_RW,
};
int i;
mutex_lock(&pdata->scm_lock);
for (i = 0; i < pdata->num_scm_regions; i++) {
if (pdata->scm_regions[i].addr != addr ||
!pdata->scm_regions[i].assigned)
continue;
if (qcom_scm_assign_mem(addr, pdata->scm_regions[i].size,
&pdata->scm_regions[i].src_perms,
&hlos, 1)) {
dev_err(pdata->dev, "SCM unassign %pa failed\n", &addr);
mutex_unlock(&pdata->scm_lock);
return;
}
pdata->scm_regions[i].assigned = false;
pdata->num_scm_regions--;
pdata->scm_regions[i] = pdata->scm_regions[pdata->num_scm_regions];
mutex_unlock(&pdata->scm_lock);
return;
}
mutex_unlock(&pdata->scm_lock);
}
static void q6apm_dai_scm_cleanup(void *data)
{
struct q6apm_dai_data *pdata = data;
int i;
for (i = pdata->num_scm_regions - 1; i >= 0; i--) {
if (pdata->scm_regions[i].assigned)
q6apm_dai_scm_unassign(pdata,
pdata->scm_regions[i].addr);
}
}
static void q6apm_dai_reserved_mem_release(void *data)
{
of_reserved_mem_device_release(data);
}
static const struct snd_pcm_hardware q6apm_dai_hardware_capture = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED |
@ -409,8 +518,11 @@ static int q6apm_dai_open(struct snd_soc_component *component,
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
size_t buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size :
BUFFER_BYTES_MAX;
ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
BUFFER_BYTES_MIN, BUFFER_BYTES_MAX);
BUFFER_BYTES_MIN, buf_max);
if (ret < 0) {
dev_err(dev, "constraint for buffer bytes min max ret = %d\n", ret);
goto err;
@ -431,17 +543,20 @@ static int q6apm_dai_open(struct snd_soc_component *component,
}
runtime->private_data = prtd;
runtime->dma_bytes = BUFFER_BYTES_MAX;
runtime->dma_bytes = pdata->has_reserved_mem ? pdata->reserved_buf_size :
BUFFER_BYTES_MAX;
if (pdata->sid < 0)
prtd->phys = substream->dma_buffer.addr;
else
prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32);
if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) {
size_t buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size :
BUFFER_BYTES_MAX;
void *pos_buffer;
prtd->pos_phys = prtd->phys + BUFFER_BYTES_MAX;
pos_buffer = (void *)(substream->dma_buffer.area + BUFFER_BYTES_MAX);
prtd->pos_phys = prtd->phys + buf_max;
pos_buffer = (void *)(substream->dma_buffer.area + buf_max);
prtd->pos_buffer = (struct sh_mem_pull_push_mode_position_buffer *)(pos_buffer);
}
@ -535,6 +650,7 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component,
{
struct q6apm_dai_data *pdata;
struct device *dev = component->dev;
size_t buf_max;
phys_addr_t phys;
int ret;
@ -544,20 +660,23 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component,
return -EINVAL;
}
buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size :
BUFFER_BYTES_MAX;
if (pdata->sid < 0)
phys = substream->dma_buffer.addr;
else
phys = substream->dma_buffer.addr | (pdata->sid << 32);
ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, BUFFER_BYTES_MAX);
ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, buf_max);
if (ret < 0)
dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret);
if (is_push_pull) {
if (pdata->sid < 0)
phys = substream->dma_buffer.addr + BUFFER_BYTES_MAX;
phys = substream->dma_buffer.addr + buf_max;
else
phys = (substream->dma_buffer.addr + BUFFER_BYTES_MAX) | (pdata->sid << 32);
phys = (substream->dma_buffer.addr + buf_max) | (pdata->sid << 32);
ret = q6apm_map_pos_buffer(dev, graph_id, phys, POS_BUFFER_BYTES);
if (ret < 0)
@ -572,20 +691,22 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component,
static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
struct q6apm_dai_data *pdata;
struct snd_pcm *pcm = rtd->pcm;
/*
* Allocate one extra page as a workaround for a DSP bug where 32-bit
* address arithmetic can overflow when the buffer is placed near the
* end of the addressable range.
*/
int size = BUFFER_BYTES_MAX + PAGE_SIZE;
int graph_id, ret;
bool is_push_pull;
struct snd_pcm_substream *substream = NULL;
pdata = snd_soc_component_get_drvdata(component);
if (!pdata)
return -EINVAL;
if (pdata->has_reserved_mem)
size = pdata->reserved_buf_size + PAGE_SIZE;
graph_id = cpu_dai->driver->id;
/* Note: DSP backend dais are uni-directional ONLY(either playback or capture) */
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
else if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
@ -603,9 +724,24 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc
if (ret)
return ret;
if (pdata->use_scm_assign && !pdata->has_reserved_mem) {
ret = q6apm_dai_scm_assign(pdata,
substream->dma_buffer.addr,
ALIGN(size, PAGE_SIZE));
if (ret) {
dev_err(component->dev,
"SCM assign buffer failed: %d\n", ret);
return ret;
}
}
ret = q6apm_dai_memory_map(component, substream, graph_id, is_push_pull);
if (ret)
if (ret) {
if (pdata->use_scm_assign && !pdata->has_reserved_mem)
q6apm_dai_scm_unassign(pdata,
substream->dma_buffer.addr);
return ret;
}
}
return 0;
@ -635,15 +771,26 @@ static void q6apm_dai_memory_unmap(struct snd_soc_component *component,
static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm)
{
struct q6apm_dai_data *pdata;
struct snd_pcm_substream *substream;
pdata = snd_soc_component_get_drvdata(component);
substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
if (substream)
if (substream) {
q6apm_dai_memory_unmap(component, substream);
if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem)
q6apm_dai_scm_unassign(pdata,
substream->dma_buffer.addr);
}
substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
if (substream)
if (substream) {
q6apm_dai_memory_unmap(component, substream);
if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem)
q6apm_dai_scm_unassign(pdata,
substream->dma_buffer.addr);
}
}
static int q6apm_dai_compr_open(struct snd_soc_component *component,
@ -683,6 +830,17 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component,
if (ret)
return ret;
if (pdata->use_scm_assign && !pdata->has_reserved_mem) {
ret = q6apm_dai_scm_assign(pdata, prtd->dma_buffer.addr,
ALIGN(size, PAGE_SIZE));
if (ret) {
dev_err(dev, "SCM assign compr buffer failed: %d\n",
ret);
snd_dma_free_pages(&prtd->dma_buffer);
return ret;
}
}
if (pdata->sid < 0)
prtd->phys = prtd->dma_buffer.addr;
else
@ -700,11 +858,16 @@ static int q6apm_dai_compr_free(struct snd_soc_component *component,
{
struct snd_compr_runtime *runtime = stream->runtime;
struct q6apm_dai_rtd *prtd = runtime->private_data;
struct q6apm_dai_data *pdata;
pdata = snd_soc_component_get_drvdata(component);
q6apm_graph_stop(prtd->graph);
q6apm_free_fragments(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK);
q6apm_unmap_memory_fixed_region(component->dev, prtd->graph->id);
q6apm_graph_close(prtd->graph);
if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem)
q6apm_dai_scm_unassign(pdata, prtd->dma_buffer.addr);
snd_dma_free_pages(&prtd->dma_buffer);
prtd->graph = NULL;
kfree(prtd);
@ -1021,6 +1184,7 @@ static int q6apm_dai_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct q6apm *apm = dev_get_drvdata(dev->parent);
struct q6apm_dai_data *pdata;
struct of_phandle_args args;
int rc;
@ -1029,12 +1193,123 @@ static int q6apm_dai_probe(struct platform_device *pdev)
if (!pdata)
return -ENOMEM;
pdata->dev = dev;
mutex_init(&pdata->scm_lock);
rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
if (rc < 0)
pdata->sid = -1;
else
pdata->sid = args.args[0] & SID_MASK_DEFAULT;
if (apm && apm->gdev &&
apm->gdev->domain_id == GPR_DOMAIN_ID_MODEM) {
if (!qcom_scm_is_available())
return -EPROBE_DEFER;
if (pdata->sid >= 0) {
dev_err(dev,
"iommus and mDSP SCM path are mutually exclusive\n");
return -EINVAL;
}
pdata->use_scm_assign = true;
rc = devm_add_action_or_reset(dev, q6apm_dai_scm_cleanup,
pdata);
if (rc)
return rc;
}
if (pdata->use_scm_assign) {
int mem_count;
mem_count = of_count_phandle_with_args(node, "memory-region",
NULL);
if (mem_count >= 1) {
struct device_node *mem_node;
struct reserved_mem *rmem;
mem_node = of_parse_phandle(node, "memory-region", 0);
rmem = of_reserved_mem_lookup(mem_node);
of_node_put(mem_node);
if (!rmem) {
dev_err(dev,
"memory-region[0]: lookup failed\n");
return -ENODEV;
}
rc = q6apm_dai_scm_assign(pdata, rmem->base,
ALIGN(rmem->size, PAGE_SIZE));
if (rc) {
dev_err(dev,
"SCM assign memory-region[0] failed: %d\n",
rc);
return rc;
}
}
if (mem_count >= 2) {
struct device_node *mem_node;
struct reserved_mem *rmem;
size_t per_stream;
mem_node = of_parse_phandle(node, "memory-region", 1);
rmem = of_reserved_mem_lookup(mem_node);
of_node_put(mem_node);
if (!rmem) {
dev_err(dev,
"memory-region[1]: lookup failed\n");
return -ENODEV;
}
per_stream = rmem->size / Q6APM_POOL_MAX_STREAMS;
if (per_stream <= POS_BUFFER_BYTES + PAGE_SIZE) {
dev_err(dev,
"reserved-memory pool too small: %pa bytes\n",
&rmem->size);
return -EINVAL;
}
/*
* Assign the whole pool to the consumer VMIDs once here,
* instead of per-stream in pcm_new()/compr_open(). Each
* qcom_scm_assign_mem() call consumes an entry in a small
* fixed-size TZ memory-protection table shared platform-wide;
* assigning per-stream-slice (up to Q6APM_POOL_MAX_STREAMS
* times) exhausts that table and hangs the SMC call.
*/
rc = q6apm_dai_scm_assign(pdata, rmem->base,
ALIGN(rmem->size, PAGE_SIZE));
if (rc) {
dev_err(dev,
"SCM assign memory-region[1] failed: %d\n",
rc);
return rc;
}
rc = of_reserved_mem_device_init_by_idx(dev, node, 1);
if (rc) {
dev_err(dev,
"reserved-memory pool init failed: %d\n",
rc);
return rc;
}
rc = devm_add_action_or_reset(dev,
q6apm_dai_reserved_mem_release,
dev);
if (rc)
return rc;
pdata->reserved_buf_size =
min_t(size_t,
per_stream - POS_BUFFER_BYTES - PAGE_SIZE,
BUFFER_BYTES_MAX);
pdata->has_reserved_mem = true;
}
}
dev_set_drvdata(dev, pdata);
return devm_snd_soc_register_component(dev, &q6apm_fe_dai_component, NULL, 0);

View File

@ -29,11 +29,13 @@ struct apm_graph_mgmt_cmd {
static struct q6apm *g_apm;
int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt,
int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt,
uint32_t rsp_opcode)
{
gpr_device_t *gdev = apm->gdev;
pkt->hdr.dest_domain = audioreach_gpr_dest_domain(gdev);
return audioreach_send_cmd_sync(&gdev->dev, gdev, &apm->result, &apm->lock,
NULL, &apm->wait, pkt, rsp_opcode);
}
@ -502,6 +504,8 @@ int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts,
mutex_unlock(&graph->lock);
pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev);
return gpr_send_port_pkt(graph->port, pkt);
}
EXPORT_SYMBOL_GPL(q6apm_write_async);
@ -536,6 +540,8 @@ int q6apm_read(struct q6apm_graph *graph)
mutex_unlock(&graph->lock);
pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev);
return gpr_send_port_pkt(graph->port, pkt);
}
EXPORT_SYMBOL_GPL(q6apm_read);
@ -900,6 +906,7 @@ static int apm_probe(gpr_device_t *gdev)
static void apm_remove(gpr_device_t *gdev)
{
g_apm = NULL;
of_platform_depopulate(&gdev->dev);
snd_soc_unregister_component(&gdev->dev);
}

View File

@ -147,7 +147,7 @@ int q6apm_alloc_fragments(struct q6apm_graph *graph,
int q6apm_free_fragments(struct q6apm_graph *graph, unsigned int dir);
int q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id);
/* Helpers */
int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt,
int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt,
uint32_t rsp_opcode);
/* Callback for graph specific */

View File

@ -57,6 +57,8 @@ struct prm_cmd_release_rsc {
static int q6prm_send_cmd_sync(struct q6prm *prm, struct gpr_pkt *pkt, uint32_t rsp_opcode)
{
pkt->hdr.dest_domain = audioreach_gpr_dest_domain(prm->gdev);
return audioreach_send_cmd_sync(prm->dev, prm->gdev, &prm->result, &prm->lock,
NULL, &prm->wait, pkt, rsp_opcode);
}