[RFC PATCH 14/23] ASoC: qcom: audioreach: send static cfg blobs to speaker protection modules

From: Srinivas Kandagatla

Date: Fri Oct 23 2026 - 13:23:13 EST


From: Ravi Hothi <ravi.hothi@xxxxxxxxxxxxxxxx>

The speaker protection modules need tuning parameters from the topology
cfg blob, which is never sent to the DSP.

Send it once the module is configured, in small SET_CFG commands split
on parameter boundaries, with each payload padded to 8 bytes as the DSP
expects. Every record is checked against the end of the blob, and a
malformed blob is rejected. A blob not laid out as parameter records is
sent whole.

Signed-off-by: Ravi Hothi <ravi.hothi@xxxxxxxxxxxxxxxx>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@xxxxxxxxxxxxxxxx>
---
sound/soc/qcom/qdsp6/audioreach.c | 139 ++++++++++++++++++++++++++++++
1 file changed, 139 insertions(+)

diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c
index 8706471e2a43..089cc9dd76fc 100644
--- a/sound/soc/qcom/qdsp6/audioreach.c
+++ b/sound/soc/qcom/qdsp6/audioreach.c
@@ -1898,6 +1898,141 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph,
return rc;
}

+/* Keep each SET_CFG small; a larger single parameter is sent on its own */
+#define AR_SP_CFG_MAX_CHUNK_BYTES 512
+
+static int audioreach_speaker_protection_send_chunk(struct q6apm_graph *graph,
+ const char *tag,
+ const void *data, int size,
+ int chunk_idx)
+{
+ int rc;
+
+ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ memcpy((void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE, data, size);
+
+ rc = q6apm_send_cmd_sync(graph->apm, pkt, 0);
+ if (rc)
+ dev_err(graph->dev, "%s cfg blob: chunk %d (%d bytes) failed: %d\n",
+ tag, chunk_idx, size, rc);
+ else
+ dev_dbg(graph->dev, "%s cfg blob: chunk %d (%d bytes) sent\n",
+ tag, chunk_idx, size);
+
+ return rc;
+}
+
+/*
+ * Walk the parameter records of a cfg blob. Each starts with the module's
+ * instance id and a param_size, followed by its payload and optional zero
+ * padding; a parameter smaller than a word may come without a payload word.
+ * Each record is checked against the end of the blob. With @dst, the records
+ * are copied there with their payload padded to 8 bytes, as the DSP expects.
+ * Returns the packed size, or -EINVAL if the blob is malformed.
+ */
+static int audioreach_sp_cfg_walk(const u32 *word, u32 num_words, u32 iid,
+ u32 first, u32 *next, u8 *dst, u32 max_bytes)
+{
+ u32 pos = first, bytes = 0;
+
+ while (pos < num_words) {
+ u32 param_size, payload_words, avail, rec_bytes;
+
+ if (word[pos] != iid || num_words - pos < 4)
+ return -EINVAL;
+
+ param_size = word[pos + 2];
+ avail = (num_words - pos - 4) * sizeof(u32);
+ if (param_size < sizeof(u32) && avail && word[pos + 4] == iid) {
+ payload_words = 0;
+ } else {
+ if (param_size > avail)
+ return -EINVAL;
+ payload_words = DIV_ROUND_UP(param_size, sizeof(u32));
+ }
+
+ rec_bytes = APM_MODULE_PARAM_DATA_SIZE + ALIGN(param_size, 8);
+ if (bytes && bytes + rec_bytes > max_bytes)
+ break;
+
+ if (dst) {
+ memcpy(dst + bytes, &word[pos], APM_MODULE_PARAM_DATA_SIZE);
+ memset(dst + bytes + APM_MODULE_PARAM_DATA_SIZE, 0,
+ rec_bytes - APM_MODULE_PARAM_DATA_SIZE);
+ memcpy(dst + bytes + APM_MODULE_PARAM_DATA_SIZE, &word[pos + 4],
+ min(param_size, payload_words * (u32)sizeof(u32)));
+ }
+ bytes += rec_bytes;
+
+ pos += 4 + payload_words;
+ while (pos < num_words && !word[pos])
+ pos++;
+ }
+
+ if (next)
+ *next = pos;
+ return bytes;
+}
+
+static int audioreach_speaker_protection_send_static_cfg(struct q6apm_graph *graph,
+ const struct audioreach_module *module,
+ const char *tag)
+{
+ u32 size, num_words, pos = 0;
+ const u32 *word;
+ int total, rc = 0, chunk = 0;
+
+ if (!module->data || !module->data->size)
+ return 0;
+
+ size = le32_to_cpu(module->data->size);
+ word = (const u32 *)module->data->data;
+ num_words = size / sizeof(u32);
+
+ /* Not laid out as parameter records, so send it as the topology built it */
+ if (size % sizeof(u32) || !num_words || word[0] != module->instance_id)
+ return audioreach_speaker_protection_send_chunk(graph, tag, word, size, 0);
+
+ total = audioreach_sp_cfg_walk(word, num_words, module->instance_id, 0,
+ NULL, NULL, U32_MAX);
+ if (total < 0) {
+ dev_err(graph->dev, "%s cfg blob is malformed\n", tag);
+ return total;
+ }
+
+ u8 *packed __free(kfree) = kmalloc(total, GFP_KERNEL);
+ if (!packed)
+ return -ENOMEM;
+
+ while (pos < num_words && !rc) {
+ int bytes = audioreach_sp_cfg_walk(word, num_words, module->instance_id,
+ pos, &pos, packed,
+ AR_SP_CFG_MAX_CHUNK_BYTES);
+ if (bytes <= 0)
+ return bytes ? bytes : -EINVAL;
+
+ rc = audioreach_speaker_protection_send_chunk(graph, tag, packed, bytes,
+ chunk++);
+ }
+
+ return rc;
+}
+
+static int audioreach_speaker_protection_static_cfg(struct q6apm_graph *graph,
+ const struct audioreach_module *module)
+{
+ return audioreach_speaker_protection_send_static_cfg(graph, module, "SP");
+}
+
+static int audioreach_speaker_protection_vi_static_cfg(struct q6apm_graph *graph,
+ const struct audioreach_module *module)
+{
+ return audioreach_speaker_protection_send_static_cfg(graph, module, "VI");
+}
+
int audioreach_set_media_format(struct q6apm_graph *graph,
const struct audioreach_module *module,
const struct audioreach_module_config *cfg)
@@ -1951,12 +2086,16 @@ int audioreach_set_media_format(struct q6apm_graph *graph,
break;
case MODULE_ID_SPEAKER_PROTECTION:
rc = audioreach_speaker_protection(graph, module);
+ if (!rc)
+ rc = audioreach_speaker_protection_static_cfg(graph, module);
if (!rc)
rc = audioreach_module_enable(graph, module, true);

break;
case MODULE_ID_SPEAKER_PROTECTION_VI:
rc = audioreach_speaker_protection_vi(graph, module, cfg);
+ if (!rc)
+ rc = audioreach_speaker_protection_vi_static_cfg(graph, module);
if (!rc)
rc = audioreach_module_enable(graph, module, true);

--
2.47.3