[RFC PATCH 08/23] ASoC: qcom: audioreach: run VI module in calibration mode with per-speaker events
From: Srinivas Kandagatla
Date: Fri Oct 23 2026 - 13:22:47 EST
From: Ravi Hothi <ravi.hothi@xxxxxxxxxxxxxxxx>
Put the speaker protection and VI modules in calibration mode and
register for EVENT_ID_VI_PER_SPKR_CALIBRATION, so each speaker's state
and measured resistance are reported on their own.
Number the V/I channel pairs consecutively, as the VI capture packs one
pair per speaker without gaps.
Signed-off-by: Ravi Hothi <ravi.hothi@xxxxxxxxxxxxxxxx>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@xxxxxxxxxxxxxxxx>
---
sound/soc/qcom/qdsp6/audioreach.c | 72 +++++++++++++++++++++++--------
sound/soc/qcom/qdsp6/audioreach.h | 36 ++++++++++++++++
sound/soc/qcom/qdsp6/q6apm.c | 55 +++++++++++++++++++++++
3 files changed, 146 insertions(+), 17 deletions(-)
diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c
index 9e45634cafbc..75995426b9f6 100644
--- a/sound/soc/qcom/qdsp6/audioreach.c
+++ b/sound/soc/qcom/qdsp6/audioreach.c
@@ -1464,6 +1464,35 @@ static int audioreach_speaker_protection(struct q6apm_graph *graph,
operation_mode);
}
+static int audioreach_register_events(struct q6apm_graph *graph,
+ const struct audioreach_module *module)
+{
+ struct apm_module_register_events *payload;
+ struct gpr_pkt *pkt;
+ int rc, payload_size;
+ void *p;
+
+ /* No event config payload, the packet is zeroed on allocation */
+ payload_size = ALIGN(sizeof(struct apm_module_register_events), 8);
+ pkt = audioreach_alloc_cmd_pkt(payload_size, APM_CMD_REGISTER_MODULE_EVENTS,
+ 0, graph->port->id, module->instance_id);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ payload = p;
+ payload->module_instance_id = module->instance_id;
+ payload->event_id = EVENT_ID_VI_PER_SPKR_CALIBRATION;
+ payload->is_register = 1;
+
+ rc = audioreach_graph_send_cmd_sync(graph, pkt, 0);
+
+ kfree(pkt);
+
+ return rc;
+}
+
static int audioreach_speaker_protection_vi(struct q6apm_graph *graph,
const struct audioreach_module *module,
const struct audioreach_module_config *mcfg)
@@ -1474,8 +1503,9 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph,
struct apm_module_sp_vi_ex_mode_cfg *ex_cfg;
int op_sz, cm_sz, ex_sz;
struct apm_module_param_data *param_data;
- int rc, i, payload_size, j;
+ int rc, i, j, payload_size;
struct gpr_pkt *pkt;
+ u32 num_speakers;
void *p;
if (num_channels > 2) {
@@ -1483,9 +1513,20 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph,
return -EINVAL;
}
- op_sz = APM_SP_VI_OP_MODE_CFG_PSIZE(num_channels);
+ /* One 32-bit V/I word per speaker, so channels == speakers */
+ num_speakers = num_channels;
+ if (!num_speakers) {
+ dev_err(graph->dev, "Error: VI needs at least one channel\n");
+ return -EINVAL;
+ }
+
+ rc = audioreach_register_events(graph, module);
+ if (rc)
+ return rc;
+
+ op_sz = APM_SP_VI_OP_MODE_CFG_PSIZE(num_speakers);
/* Channel mapping for Isense and Vsense, thus twice number of speakers. */
- cm_sz = APM_SP_VI_CH_MAP_CFG_PSIZE(num_channels * 2);
+ cm_sz = APM_SP_VI_CH_MAP_CFG_PSIZE(num_speakers * 2);
ex_sz = APM_SP_VI_EX_MODE_CFG_PSIZE;
payload_size = op_sz + cm_sz + ex_sz;
@@ -1503,8 +1544,9 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph,
param_data->param_id = PARAM_ID_SP_VI_OP_MODE_CFG;
param_data->param_size = op_sz - APM_MODULE_PARAM_DATA_SIZE;
- op_cfg->cfg.num_channels = num_channels;
- op_cfg->cfg.operation_mode = PARAM_ID_SP_VI_OP_MODE_NORMAL;
+ /* The DSP calls this field num_speakers for the VI module */
+ op_cfg->cfg.num_channels = num_speakers;
+ op_cfg->cfg.operation_mode = PARAM_ID_SP_VI_OP_MODE_CALIBRATION;
p += op_sz;
cm_cfg = p;
@@ -1514,20 +1556,15 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph,
param_data->param_id = PARAM_ID_SP_VI_CHANNEL_MAP_CFG;
param_data->param_size = cm_sz - APM_MODULE_PARAM_DATA_SIZE;
- cm_cfg->cfg.num_channels = num_channels * 2;
- /* Convert the physical mapping to a logical mapping of the channels */
- for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < num_channels; i++) {
+ cm_cfg->cfg.num_channels = num_speakers * 2;
+ /* Number the V/I pairs consecutively, the capture has no gaps */
+ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < num_speakers; i++) {
if (!mcfg->channel_map[i])
continue;
- /*
- * Map speakers into Vsense and then Isense of each channel.
- * E.g. for PCM_CHANNEL_FL and PCM_CHANNEL_FR to:
- * [1, 2, 3, 4]
- */
- cm_cfg->cfg.channel_mapping[2 * j] = (mcfg->channel_map[i] - 1) * 2 + 1;
- cm_cfg->cfg.channel_mapping[2 * j + 1] = (mcfg->channel_map[i] - 1) * 2 + 2;
- ++j;
+ cm_cfg->cfg.channel_mapping[2 * j] = 2 * j + 1;
+ cm_cfg->cfg.channel_mapping[2 * j + 1] = 2 * j + 2;
+ j++;
}
p += cm_sz;
@@ -1601,7 +1638,7 @@ int audioreach_set_media_format(struct q6apm_graph *graph,
break;
case MODULE_ID_SPEAKER_PROTECTION:
rc = audioreach_speaker_protection(graph, module,
- PARAM_ID_SP_OP_MODE_NORMAL);
+ PARAM_ID_SP_OP_MODE_CALIBRATION);
if (!rc)
rc = audioreach_module_enable(graph, module, true);
@@ -1610,6 +1647,7 @@ int audioreach_set_media_format(struct q6apm_graph *graph,
rc = audioreach_speaker_protection_vi(graph, module, cfg);
if (!rc)
rc = audioreach_module_enable(graph, module, true);
+
break;
case MODULE_ID_AUDIO_IF_SOURCE:
case MODULE_ID_AUDIO_IF_SINK:
diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h
index 38b71aefd845..4744d3117010 100644
--- a/sound/soc/qcom/qdsp6/audioreach.h
+++ b/sound/soc/qcom/qdsp6/audioreach.h
@@ -720,6 +720,42 @@ struct param_id_sp_vi_channel_map_cfg {
uint32_t channel_mapping[];
} __packed;
+/* VI per-speaker calibration event, raised every frame in calibration mode */
+#define EVENT_ID_VI_PER_SPKR_CALIBRATION 0x08001511
+
+/* Calibration state, as reported by struct vi_per_spkr_calib_param.state */
+#define VI_CALIBRATION_STATE_NORMAL_MODE 0 /* Not in calibration mode */
+#define VI_CALIBRATION_STATE_INACTIVE 1 /* Port not started */
+#define VI_CALIBRATION_STATE_WARM_UP 2 /* Warming up */
+#define VI_CALIBRATION_STATE_IN_PROGRESS 3 /* Calibration running */
+#define VI_CALIBRATION_STATE_SUCCESS 4 /* R0/T0 within range */
+#define VI_CALIBRATION_STATE_FAILED 5 /* R0/T0 out of range */
+#define VI_CALIBRATION_STATE_WAIT_FOR_VI 6 /* Waiting for V/I data */
+#define VI_CALIBRATION_STATE_VI_WAIT_TIMEOUT 7 /* Timed out waiting for V/I */
+#define VI_CALIBRATION_STATE_LOW_VI 8 /* V/I signal level too low */
+
+/**
+ * struct vi_per_spkr_calib_param - Per-speaker calibration result
+ * @state: Calibration state of this speaker.
+ * @r0_cali_q24: Measured DC resistance in ohms, Q24. Filled in regardless of
+ * @state, so it is meaningful for both VI_CALIBRATION_STATE_SUCCESS
+ * and VI_CALIBRATION_STATE_FAILED.
+ */
+struct vi_per_spkr_calib_param {
+ uint32_t state;
+ int32_t r0_cali_q24;
+} __packed;
+
+/**
+ * struct event_id_vi_per_spkr_calibration - Per-speaker VI calibration result
+ * @num_ch: Number of speakers the result covers.
+ * @cali_param: Per-speaker calibration state and measured resistance.
+ */
+struct event_id_vi_per_spkr_calibration {
+ uint32_t num_ch;
+ struct vi_per_spkr_calib_param cali_param[];
+} __packed;
+
#define PARAM_ID_SAL_OUTPUT_CFG 0x08001016
struct param_id_sal_output_config {
uint32_t bits_per_sample;
diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c
index bb2f18a64644..0e02cadbc14b 100644
--- a/sound/soc/qcom/qdsp6/q6apm.c
+++ b/sound/soc/qcom/qdsp6/q6apm.c
@@ -560,6 +560,58 @@ int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir)
}
EXPORT_SYMBOL_GPL(q6apm_get_hw_pointer);
+/*
+ * Convert a Q24 fixed-point value to its integer part, and to the first three
+ * decimal places of its fractional part.
+ */
+static int q6apm_q24_to_int_frac(int32_t val, int *frac)
+{
+ *frac = (int)(((s64)(val & 0xffffff) * 1000) >> 24);
+
+ return val >> 24;
+}
+
+static void q6apm_vi_calibration_event(struct device *dev,
+ const struct apm_module_event *event,
+ int payload_size)
+{
+ const struct event_id_vi_per_spkr_calibration *cali;
+ unsigned int num_ch, i;
+ int r0_int, r0_frac;
+
+ if (payload_size < 0 || payload_size < (int)(sizeof(*event) + sizeof(*cali))) {
+ dev_err(dev, "VI calibration event truncated (%d bytes)\n",
+ payload_size);
+ return;
+ }
+
+ cali = (const struct event_id_vi_per_spkr_calibration *)(event + 1);
+
+ /* Bound the array by the received payload, not by num_ch alone */
+ num_ch = (payload_size - sizeof(*event) - sizeof(*cali)) / sizeof(cali->cali_param[0]);
+ num_ch = min_t(unsigned int, cali->num_ch, num_ch);
+
+ for (i = 0; i < num_ch; i++) {
+ /* Only SUCCESS and FAILED carry a measured R0; both are worth reporting */
+ switch (cali->cali_param[i].state) {
+ case VI_CALIBRATION_STATE_SUCCESS:
+ break;
+ case VI_CALIBRATION_STATE_FAILED:
+ dev_dbg(dev, "VI calibration channel %u failed, R0/T0 out of range\n", i);
+ break;
+ default:
+ dev_dbg(dev, "VI calibration channel %u in state %u\n",
+ i, cali->cali_param[i].state);
+ continue;
+ }
+
+ r0_int = q6apm_q24_to_int_frac(cali->cali_param[i].r0_cali_q24, &r0_frac);
+ /* Raised every frame, so keep it at debug level */
+ dev_dbg(dev, "VI calibration channel %u: R0 %d.%03d ohms\n",
+ i, r0_int, r0_frac);
+ }
+}
+
static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op)
{
struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done_v2 *rd_done;
@@ -583,6 +635,9 @@ static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op)
client_event = APM_CLIENT_EVENT_WATERMARK_EVENT;
graph->cb(client_event, hdr->token, data->payload, graph->priv);
break;
+ case EVENT_ID_VI_PER_SPKR_CALIBRATION:
+ q6apm_vi_calibration_event(dev, event, data->payload_size);
+ break;
}
break;
--
2.47.3