summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sound/soc/qcom/Kconfig1
-rw-r--r--sound/soc/qcom/qdsp6/q6apm-dai.c317
2 files changed, 297 insertions, 21 deletions
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index e6e24f3b9922..991feb317940 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -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
diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c
index 033197bdd870..b0b10ba51c99 100644
--- a/sound/soc/qcom/qdsp6/q6apm-dai.c
+++ b/sound/soc/qcom/qdsp6/q6apm-dai.c
@@ -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);