[RFC PATCH 13/23] ASoC: qcom: audioreach: send cached R0/T0 to the VI module in normal mode
From: Srinivas Kandagatla
Date: Fri Oct 23 2026 - 13:27:04 EST
From: Ravi Hothi <ravi.hothi@xxxxxxxxxxxxxxxx>
In normal mode the VI module needs each speaker's calibrated resistance
and temperature, otherwise thermal protection has no reference.
Send the stored values with PARAM_ID_SP_TH_VI_R0T0_CFG in normal mode
and store the result of each successful calibration. Values written
from userspace are range checked, and a graph whose speaker count does
not match the stored set is refused instead of being partly configured.
Signed-off-by: Ravi Hothi <ravi.hothi@xxxxxxxxxxxxxxxx>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@xxxxxxxxxxxxxxxx>
---
sound/soc/qcom/qdsp6/audioreach.c | 170 ++++++++++++++++++++++++++++++
sound/soc/qcom/qdsp6/audioreach.h | 21 ++++
2 files changed, 191 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c
index 7c15c91888b7..8706471e2a43 100644
--- a/sound/soc/qcom/qdsp6/audioreach.c
+++ b/sound/soc/qcom/qdsp6/audioreach.c
@@ -32,6 +32,15 @@ struct sp_vi_r0_result {
static struct sp_vi_r0_result sp_vi_r0;
static DEFINE_MUTEX(sp_vi_r0_lock);
+struct sp_vi_r0t0_cfg {
+ u32 num_ch;
+ s32 r0_cali_q24[MAX_SP_VI_SPEAKERS];
+ s16 t0_cali_q6[MAX_SP_VI_SPEAKERS];
+};
+
+static struct sp_vi_r0t0_cfg sp_vi_r0t0_cfg;
+static DEFINE_MUTEX(sp_vi_r0t0_cfg_lock);
+
/* SubGraph Config */
struct apm_sub_graph_data {
struct apm_sub_graph_cfg sub_graph_cfg;
@@ -282,6 +291,15 @@ struct apm_module_sp_vi_channel_map_cfg {
sizeof(struct apm_module_sp_vi_channel_map_cfg) + \
(ch) * sizeof(uint32_t), 8)
+struct apm_module_sp_th_vi_r0t0_cfg {
+ struct apm_module_param_data param_data;
+ struct param_id_sp_th_vi_r0t0_cfg cfg;
+} __packed;
+
+#define APM_SP_TH_VI_R0T0_CFG_PSIZE(ch) ALIGN( \
+ sizeof(struct apm_module_sp_th_vi_r0t0_cfg) + \
+ (ch) * sizeof(struct vi_r0t0_cfg), 8)
+
static void *__audioreach_alloc_pkt(int payload_size, uint32_t opcode, uint32_t token,
uint32_t src_port, uint32_t dest_port, bool has_cmd_hdr)
{
@@ -1527,6 +1545,86 @@ static int sp_vi_q24_to_int_frac(s32 val, int *frac)
return val >> 24;
}
+#define SP_VI_R0_MIN_Q24 (1 << 22) /* 0.25 ohm */
+#define SP_VI_R0_MAX_Q24 (64 << 24) /* 64 ohms */
+
+#define SP_VI_T0_MIN_Q6 (-30 * 64)
+#define SP_VI_T0_MAX_Q6 (80 * 64)
+
+void audioreach_get_sp_vi_r0t0(long *vals, unsigned int count)
+{
+ unsigned int i;
+
+ memset(vals, 0, count * sizeof(*vals));
+
+ guard(mutex)(&sp_vi_r0t0_cfg_lock);
+ for (i = 0; i < sp_vi_r0t0_cfg.num_ch && 2 * i + 1 < count; i++) {
+ vals[2 * i] = sp_vi_r0t0_cfg.r0_cali_q24[i];
+ vals[2 * i + 1] = sp_vi_r0t0_cfg.t0_cali_q6[i];
+ }
+}
+
+int audioreach_set_sp_vi_r0t0(struct device *dev, const long *vals, unsigned int count)
+{
+ struct sp_vi_r0t0_cfg new = {};
+ unsigned int i, num_ch;
+
+ /* One R0 and one T0 per channel, so an odd count cannot be complete. */
+ if (!count || count % 2)
+ return -EINVAL;
+
+ num_ch = min(count / 2, (unsigned int)MAX_SP_VI_SPEAKERS);
+
+ /* A trailing all-zero pair is padding, as zero is not a plausible R0 */
+ while (num_ch && !vals[2 * (num_ch - 1)] && !vals[2 * (num_ch - 1) + 1])
+ num_ch--;
+
+ if (!num_ch) {
+ dev_err(dev, "SP R0T0 has no channels set\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < num_ch; i++) {
+ long r0 = vals[2 * i];
+ long t0 = vals[2 * i + 1];
+ int r0_int, r0_frac;
+
+ if (r0 < SP_VI_R0_MIN_Q24 || r0 > SP_VI_R0_MAX_Q24) {
+ r0_int = sp_vi_q24_to_int_frac(r0, &r0_frac);
+ dev_err(dev, "Channel %u R0 %d.%03d ohms (Q24 %ld) out of range\n",
+ i, r0_int, r0_frac, r0);
+ return -ERANGE;
+ }
+
+ if (t0 < SP_VI_T0_MIN_Q6 || t0 > SP_VI_T0_MAX_Q6) {
+ dev_err(dev, "Channel %u T0 %ld.%03ld degC (Q6 %ld) out of range\n",
+ i, t0 / 64, abs((t0 % 64) * 1000 / 64), t0);
+ return -ERANGE;
+ }
+
+ new.r0_cali_q24[i] = r0;
+ new.t0_cali_q6[i] = t0;
+ }
+ new.num_ch = num_ch;
+
+ guard(mutex)(&sp_vi_r0t0_cfg_lock);
+
+ sp_vi_r0t0_cfg = new;
+
+ for (i = 0; i < num_ch; i++) {
+ int r0_int, r0_frac;
+
+ r0_int = sp_vi_q24_to_int_frac(new.r0_cali_q24[i], &r0_frac);
+ dev_info(dev, "SP channel %u: R0 %d.%03d ohms (Q24 %d), T0 %d.%03d degC (Q6 %d)\n",
+ i, r0_int, r0_frac, new.r0_cali_q24[i],
+ new.t0_cali_q6[i] / 64,
+ abs((new.t0_cali_q6[i] % 64) * 1000 / 64),
+ new.t0_cali_q6[i]);
+ }
+
+ return num_ch;
+}
+
void audioreach_vi_calibration_event(struct device *dev,
const struct event_id_vi_per_spkr_calibration *cali,
u32 num_ch)
@@ -1583,6 +1681,14 @@ void audioreach_vi_calibration_event(struct device *dev,
i, r0_int, r0_frac, sp_vi_r0.r0_cali_q24[i]);
}
+ /* Only a run where every speaker succeeded gives a usable R0/T0 set */
+ if (success) {
+ guard(mutex)(&sp_vi_r0t0_cfg_lock);
+ sp_vi_r0t0_cfg.num_ch = num_ch;
+ for (i = 0; i < num_ch; i++)
+ sp_vi_r0t0_cfg.r0_cali_q24[i] = cali->cali_param[i].r0_cali_q24;
+ }
+
dev_info(dev, "VI calibration complete: %s\n", success ? "success" : "failed");
}
@@ -1725,6 +1831,70 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph,
kfree(pkt);
+ if (rc)
+ return rc;
+
+ if (sp_operation_mode == PARAM_ID_SP_VI_OP_MODE_NORMAL) {
+ struct apm_module_sp_th_vi_r0t0_cfg *r0t0_cfg;
+ int r0t0_sz;
+
+ guard(mutex)(&sp_vi_r0t0_cfg_lock);
+
+ if (!sp_vi_r0t0_cfg.num_ch)
+ return 0;
+
+ if (sp_vi_r0t0_cfg.num_ch != num_speakers) {
+ dev_err(graph->dev,
+ "SP R0T0 is for %u speakers, graph has %u\n",
+ sp_vi_r0t0_cfg.num_ch, num_speakers);
+ return -EINVAL;
+ }
+
+ r0t0_sz = APM_SP_TH_VI_R0T0_CFG_PSIZE(sp_vi_r0t0_cfg.num_ch);
+ pkt = audioreach_alloc_apm_cmd_pkt(r0t0_sz, APM_CMD_SET_CFG, 0);
+ if (IS_ERR(pkt))
+ return PTR_ERR(pkt);
+
+ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE;
+
+ r0t0_cfg = p;
+ param_data = &r0t0_cfg->param_data;
+ param_data->module_instance_id = module->instance_id;
+ param_data->error_code = 0;
+ param_data->param_id = PARAM_ID_SP_TH_VI_R0T0_CFG;
+ param_data->param_size = r0t0_sz - APM_MODULE_PARAM_DATA_SIZE;
+
+ r0t0_cfg->cfg.num_ch = sp_vi_r0t0_cfg.num_ch;
+ for (i = 0; i < sp_vi_r0t0_cfg.num_ch; i++) {
+ r0t0_cfg->cfg.r0t0_cfg[i].r0_cali_q24 = sp_vi_r0t0_cfg.r0_cali_q24[i];
+ r0t0_cfg->cfg.r0t0_cfg[i].t0_cali_q6 = sp_vi_r0t0_cfg.t0_cali_q6[i];
+ r0t0_cfg->cfg.r0t0_cfg[i].reserved = 0;
+ }
+
+ rc = q6apm_send_cmd_sync(graph->apm, pkt, 0);
+
+ kfree(pkt);
+
+ if (rc) {
+ dev_err(graph->dev, "SP activation failed: %d\n", rc);
+ return rc;
+ }
+
+ /* Protection is now active on the DSP */
+ dev_info(graph->dev, "SP active in Normal mode (%u channels)\n",
+ sp_vi_r0t0_cfg.num_ch);
+ for (i = 0; i < sp_vi_r0t0_cfg.num_ch; i++) {
+ int r0_int, r0_frac;
+
+ r0_int = sp_vi_q24_to_int_frac(sp_vi_r0t0_cfg.r0_cali_q24[i], &r0_frac);
+ dev_info(graph->dev,
+ " SP ch%u: R0 %d.%03d ohms, T0 %d.%03d degC\n",
+ i, r0_int, r0_frac,
+ sp_vi_r0t0_cfg.t0_cali_q6[i] / 64,
+ abs((sp_vi_r0t0_cfg.t0_cali_q6[i] % 64) * 1000 / 64));
+ }
+ }
+
return rc;
}
diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h
index e30603674e37..67a9d3c66fd4 100644
--- a/sound/soc/qcom/qdsp6/audioreach.h
+++ b/sound/soc/qcom/qdsp6/audioreach.h
@@ -722,6 +722,25 @@ struct param_id_sp_vi_channel_map_cfg {
uint32_t channel_mapping[];
} __packed;
+#define PARAM_ID_SP_TH_VI_R0T0_CFG 0x080011f5
+
+/**
+ * struct vi_r0t0_cfg - Per-speaker calibrated R0/T0
+ * @r0_cali_q24: Calibrated DC resistance in ohms, Q24.
+ * @t0_cali_q6: Calibrated temperature in degrees C, Q6.
+ * @reserved: Padding, must be zero.
+ */
+struct vi_r0t0_cfg {
+ int32_t r0_cali_q24;
+ int16_t t0_cali_q6;
+ int16_t reserved;
+} __packed;
+
+struct param_id_sp_th_vi_r0t0_cfg {
+ uint32_t num_ch;
+ struct vi_r0t0_cfg r0t0_cfg[];
+} __packed;
+
/* VI per-speaker calibration event, raised every frame in calibration mode */
#define EVENT_ID_VI_PER_SPKR_CALIBRATION 0x08001511
@@ -1116,6 +1135,8 @@ int audioreach_send_u32_param(struct q6apm_graph *graph,
uint32_t param_id, uint32_t param_val);
u32 audioreach_get_sp_operation_mode(void);
void audioreach_set_sp_operation_mode(u32 mode);
+void audioreach_get_sp_vi_r0t0(long *vals, unsigned int count);
+int audioreach_set_sp_vi_r0t0(struct device *dev, const long *vals, unsigned int count);
void audioreach_vi_calibration_event(struct device *dev,
const struct event_id_vi_per_spkr_calibration *cali,
u32 num_ch);
--
2.47.3