[RFC PATCH 11/23] ASoC: qcom: audioreach: cache VI calibration results per speaker

From: Srinivas Kandagatla

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


From: Ravi Hothi <ravi.hothi@xxxxxxxxxxxxxxxx>

The VI module reports EVENT_ID_VI_PER_SPKR_CALIBRATION every frame for
the whole run. The handler logged each event and kept nothing, flooding
the log and dropping the measured resistance.

Log each state once per speaker and keep the result once every speaker
has reached a final state, with R0 for those that succeeded. Log the
R0 of a failed speaker as out of range. Move the handler to audioreach.c
next to the state it keeps, and turn the calibration states into a
documented enum.

Signed-off-by: Ravi Hothi <ravi.hothi@xxxxxxxxxxxxxxxx>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@xxxxxxxxxxxxxxxx>
---
sound/soc/qcom/qdsp6/audioreach.c | 129 ++++++++++++++++++++++++++++++
sound/soc/qcom/qdsp6/audioreach.h | 39 ++++++---
sound/soc/qcom/qdsp6/q6apm.c | 37 +--------
3 files changed, 162 insertions(+), 43 deletions(-)

diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c
index 24198cde8158..eb317b704b54 100644
--- a/sound/soc/qcom/qdsp6/audioreach.c
+++ b/sound/soc/qcom/qdsp6/audioreach.c
@@ -2,6 +2,7 @@
// Copyright (c) 2020, Linaro Limited

#include <linux/kernel.h>
+#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/soc/qcom/apr.h>
#include <sound/soc.h>
@@ -12,6 +13,23 @@
#include "q6apm.h"
#include "audioreach.h"

+enum sp_vi_cali_state {
+ SP_VI_CALI_IDLE, /* Not calibrating */
+ SP_VI_CALI_RUNNING, /* Events arriving, none decisive yet */
+ SP_VI_CALI_FAILED, /* Run finished, no usable R0 */
+ SP_VI_CALI_SUCCESS, /* Run finished, @r0_cali_q24 valid */
+};
+
+struct sp_vi_r0_result {
+ enum sp_vi_cali_state state;
+ u32 num_ch;
+ s32 r0_cali_q24[MAX_SP_VI_SPEAKERS];
+ u32 seen_states[MAX_SP_VI_SPEAKERS];
+};
+
+static struct sp_vi_r0_result sp_vi_r0;
+static DEFINE_MUTEX(sp_vi_r0_lock);
+
/* SubGraph Config */
struct apm_sub_graph_data {
struct apm_sub_graph_cfg sub_graph_cfg;
@@ -1456,6 +1474,106 @@ static int audioreach_gain_set(struct q6apm_graph *graph,
return q6apm_send_cmd_sync(graph->apm, pkt, 0);
}

+static bool sp_vi_cali_state_is_failure(u32 state)
+{
+ return state == VI_CALIBRATION_STATE_FAILED ||
+ state == VI_CALIBRATION_STATE_VI_WAIT_TIMED_OUT;
+}
+
+static bool sp_vi_cali_state_is_terminal(u32 state)
+{
+ return state == VI_CALIBRATION_STATE_SUCCESS ||
+ sp_vi_cali_state_is_failure(state);
+}
+
+#define SP_VI_CALI_UNKNOWN_STATE_BIT (VI_CALIBRATION_STATE_LOW_VI + 1)
+
+static const char *sp_vi_cali_state_name(u32 state)
+{
+ static const char * const names[] = {
+ [VI_CALIBRATION_STATE_INCORRECT_OP_MODE] = "not in calibration mode",
+ [VI_CALIBRATION_STATE_INACTIVE] = "inactive",
+ [VI_CALIBRATION_STATE_WARMUP] = "warming up",
+ [VI_CALIBRATION_STATE_INPROGRESS] = "in progress",
+ [VI_CALIBRATION_STATE_SUCCESS] = "success",
+ [VI_CALIBRATION_STATE_FAILED] = "failed, R0/T0 out of range",
+ [VI_CALIBRATION_STATE_WAIT_FOR_VI] = "waiting for V/I",
+ [VI_CALIBRATION_STATE_VI_WAIT_TIMED_OUT] = "failed, timed out waiting for V/I",
+ [VI_CALIBRATION_STATE_LOW_VI] = "V/I level too low",
+ };
+
+ if (state >= ARRAY_SIZE(names) || !names[state])
+ return "unknown";
+
+ return names[state];
+}
+
+static int sp_vi_q24_to_int_frac(s32 val, int *frac)
+{
+ *frac = (int)(((s64)(val & 0xffffff) * 1000) >> 24);
+
+ return val >> 24;
+}
+
+void audioreach_vi_calibration_event(struct device *dev,
+ const struct event_id_vi_per_spkr_calibration *cali,
+ u32 num_ch)
+{
+ bool decisive = true;
+ bool success = true;
+ unsigned int i;
+ u32 bit;
+
+ guard(mutex)(&sp_vi_r0_lock);
+
+ if (sp_vi_r0.state != SP_VI_CALI_RUNNING)
+ return;
+
+ for (i = 0; i < num_ch; i++) {
+ u32 state = cali->cali_param[i].state;
+
+ if (sp_vi_cali_state_is_failure(state))
+ success = false;
+ else if (!sp_vi_cali_state_is_terminal(state))
+ decisive = false;
+
+ bit = BIT(min_t(u32, state, SP_VI_CALI_UNKNOWN_STATE_BIT));
+ if (!(sp_vi_r0.seen_states[i] & bit)) {
+ sp_vi_r0.seen_states[i] |= bit;
+ dev_info(dev, "VI calibration channel %u: %s\n",
+ i, sp_vi_cali_state_name(state));
+ }
+ }
+
+ if (!decisive)
+ return;
+
+ sp_vi_r0.state = success ? SP_VI_CALI_SUCCESS : SP_VI_CALI_FAILED;
+ sp_vi_r0.num_ch = num_ch;
+
+ for (i = 0; i < num_ch; i++) {
+ int r0_int, r0_frac;
+
+ if (cali->cali_param[i].state == VI_CALIBRATION_STATE_FAILED) {
+ r0_int = sp_vi_q24_to_int_frac(cali->cali_param[i].r0_cali_q24,
+ &r0_frac);
+ dev_warn(dev, "VI calibration channel %u: R0 %d.%03d ohms out of range\n",
+ i, r0_int, r0_frac);
+ continue;
+ }
+ if (sp_vi_cali_state_is_failure(cali->cali_param[i].state))
+ continue;
+
+ sp_vi_r0.r0_cali_q24[i] = cali->cali_param[i].r0_cali_q24;
+
+ r0_int = sp_vi_q24_to_int_frac(sp_vi_r0.r0_cali_q24[i], &r0_frac);
+ dev_info(dev, "VI calibration channel %u: R0 %d.%03d ohms (Q24 %d)\n",
+ i, r0_int, r0_frac, sp_vi_r0.r0_cali_q24[i]);
+ }
+
+ dev_info(dev, "VI calibration complete: %s\n", success ? "success" : "failed");
+}
+
static int audioreach_speaker_protection(struct q6apm_graph *graph,
const struct audioreach_module *module,
uint32_t operation_mode)
@@ -1493,6 +1611,15 @@ static int audioreach_register_events(struct q6apm_graph *graph,
return rc;
}

+static void audioreach_arm_vi_calibration(void)
+{
+ guard(mutex)(&sp_vi_r0_lock);
+
+ sp_vi_r0 = (struct sp_vi_r0_result){
+ .state = SP_VI_CALI_RUNNING,
+ };
+}
+
static int audioreach_speaker_protection_vi(struct q6apm_graph *graph,
const struct audioreach_module *module,
const struct audioreach_module_config *mcfg)
@@ -1520,6 +1647,8 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph,
return -EINVAL;
}

+ audioreach_arm_vi_calibration();
+
rc = audioreach_register_events(graph, module);
if (rc)
return rc;
diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h
index c8c7fa4f6f70..232faba6d83f 100644
--- a/sound/soc/qcom/qdsp6/audioreach.h
+++ b/sound/soc/qcom/qdsp6/audioreach.h
@@ -725,16 +725,32 @@ struct param_id_sp_vi_channel_map_cfg {
/* 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 */
+/**
+ * enum vi_calibration_state - Calibration state of one speaker
+ * @VI_CALIBRATION_STATE_INCORRECT_OP_MODE: Not in calibration operation mode
+ * @VI_CALIBRATION_STATE_INACTIVE: Port not started
+ * @VI_CALIBRATION_STATE_WARMUP: Warming up
+ * @VI_CALIBRATION_STATE_INPROGRESS: Calibration running
+ * @VI_CALIBRATION_STATE_SUCCESS: R0/T0 within range
+ * @VI_CALIBRATION_STATE_FAILED: R0/T0 out of range
+ * @VI_CALIBRATION_STATE_WAIT_FOR_VI: Waiting for the V/I level to rise
+ * @VI_CALIBRATION_STATE_VI_WAIT_TIMED_OUT: Gave up waiting for the V/I level
+ * @VI_CALIBRATION_STATE_LOW_VI: V/I level fell below the threshold mid-run
+ *
+ * Reported by struct vi_per_spkr_calib_param.state. The DSP chooses these
+ * values, so they are fixed and must not be renumbered.
+ */
+enum vi_calibration_state {
+ VI_CALIBRATION_STATE_INCORRECT_OP_MODE = 0,
+ VI_CALIBRATION_STATE_INACTIVE = 1,
+ VI_CALIBRATION_STATE_WARMUP = 2,
+ VI_CALIBRATION_STATE_INPROGRESS = 3,
+ VI_CALIBRATION_STATE_SUCCESS = 4,
+ VI_CALIBRATION_STATE_FAILED = 5,
+ VI_CALIBRATION_STATE_WAIT_FOR_VI = 6,
+ VI_CALIBRATION_STATE_VI_WAIT_TIMED_OUT = 7,
+ VI_CALIBRATION_STATE_LOW_VI = 8,
+};

/**
* struct vi_per_spkr_calib_param - Per-speaker calibration result
@@ -1098,6 +1114,9 @@ int audioreach_gain_set_vol_ctrl(struct q6apm *apm,
int audioreach_send_u32_param(struct q6apm_graph *graph,
const struct audioreach_module *module,
uint32_t param_id, uint32_t param_val);
+void audioreach_vi_calibration_event(struct device *dev,
+ const struct event_id_vi_per_spkr_calibration *cali,
+ u32 num_ch);
int audioreach_compr_set_param(struct q6apm_graph *graph,
const struct audioreach_module_config *mcfg);
int audioreach_setup_push_pull(struct q6apm_graph *graph, phys_addr_t bphys,
diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c
index 0e02cadbc14b..a4502741d87f 100644
--- a/sound/soc/qcom/qdsp6/q6apm.c
+++ b/sound/soc/qcom/qdsp6/q6apm.c
@@ -560,24 +560,12 @@ 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;
+ u32 num_ch;

if (payload_size < 0 || payload_size < (int)(sizeof(*event) + sizeof(*cali))) {
dev_err(dev, "VI calibration event truncated (%d bytes)\n",
@@ -589,27 +577,10 @@ static void q6apm_vi_calibration_event(struct device *dev,

/* 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;
- }
+ num_ch = min_t(u32, cali->num_ch, num_ch);
+ num_ch = min_t(u32, num_ch, MAX_SP_VI_SPEAKERS);

- 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);
- }
+ audioreach_vi_calibration_event(dev, cali, num_ch);
}

static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op)
--
2.47.3