Skip to content

WIP: multi-slot WOV arbiter with VAD gate and D0I3/S0iX support - #11107

Draft
lgirdwood wants to merge 10 commits into
thesofproject:mainfrom
lgirdwood:feature/wov-multi-kpb-v2
Draft

WIP: multi-slot WOV arbiter with VAD gate and D0I3/S0iX support#11107
lgirdwood wants to merge 10 commits into
thesofproject:mainfrom
lgirdwood:feature/wov-multi-kpb-v2

Conversation

@lgirdwood

@lgirdwood lgirdwood commented Aug 20, 2026

Copy link
Copy Markdown
Member

Multi-Slot Wake-On-Voice (WOV) Architecture & Arbitration

Overview

The Multi-Slot WOV subsystem lets a single DMIC feed up to 3 concurrent keyword detectors
running on the DSP. A single shared KPB sits between the VAD gate and the fan-out mixin,
recording a pre-roll window (6 seconds on TigerLake, 2.1 seconds on other platforms) for all
three slots in one 192 KB ring buffer — versus 576 KB in a per-slot design. When any detector
fires the wov_arbiter drains the KPB ring buffer to the host via an ALSA compress device and
pauses the other detectors. When the host closes the stream the arbiter resumes all detectors.

The ALSA compress framework (snd_compr) decouples HDA DMA from the audio sample-rate clock.
During the pre-roll burst the KPB draining EDF task fills HDA DMA fragments as fast as the bus allows
rather than at the rate-locked 16 kHz × sample-size pace of a regular PCM device.
After the pre-roll drains the live DMIC stream continues over the same compress device at realtime rate.

This design keeps host DMA live and eliminates the wakeup latency normally incurred by starting
DMA after detection.


System Architecture

Component Graph

graph TD
    subgraph P100["Pipeline 100 — Capture, Gating & Pre-Roll  (Core 0)"]
        DAI["DAI Copier\nHDA Analog\ndai_index=1"]
        VAD["vad_gate\n(energy estimator)"]
        KPB["kpb\n**shared** 192 KB ring buffer\n(6 s pre-roll for all slots)"]
        MIX["mixin\n(1→3 fan-out)"]
        DAI --> VAD --> KPB --> MIX
    end

    subgraph P101["Pipeline 101 — Slot 0  (Core 0)"]
        MO0["mixout 0"]
        D0["detect_test\nSlot 0\n(Male 80–170 Hz)"]
        MO0 --> D0
    end

    subgraph P102["Pipeline 102 — Slot 1  (Core 0)"]
        MO1["mixout 1"]
        D1["detect_test\nSlot 1\n(Female 175–270 Hz)"]
        MO1 --> D1
    end

    subgraph P103["Pipeline 103 — Slot 2  (Core 1)"]
        MO2["mixout 2"]
        D2["detect_test\nSlot 2\n(Child 275–500 Hz)"]
        MO2 --> D2
    end

    subgraph P104["Pipeline 104 — Arbitration & Host Capture  (Core 0)"]
        ARB["wov_arbiter\n(first-wins)"]
        HC["host-copier\ncomprC0D11\n'DMIC Multi-WOV'"]
        ARB --> HC
    end

    MIX --> MO0
    MIX --> MO1
    MIX --> MO2

    D0 --> ARB
    D1 --> ARB
    D2 --> ARB

    D0 -- "Notifier WOV_DETECT\n(slot_id=0)" --> ARB
    D1 -- "Notifier WOV_DETECT\n(slot_id=1)" --> ARB
    D2 -- "Notifier WOV_DETECT\n(slot_id=2)" --> ARB
    ARB -- "Notifier WOV_CTRL\n(PAUSE/RESUME)" --> D0
    ARB -- "Notifier WOV_CTRL\n(PAUSE/RESUME)" --> D1
    ARB -- "Notifier WOV_CTRL\n(PAUSE/RESUME)" --> D2

    %% Row layout hints (invisible ~~~ edges — dagre rank control)
    %% Row 0: P100  |  Row 1: P101 / P102 / P103  |  Row 2: P104
    KPB ~~~ MO0
    MO0 ~~~ ARB

    style VAD fill:#2d5a27,stroke:#555
    style KPB fill:#4a3a00,stroke:#555
    style ARB fill:#1c4966,stroke:#555
    style D0  fill:#663300,stroke:#555
    style D1  fill:#660033,stroke:#555
    style D2  fill:#003366,stroke:#555
Loading

Pipeline State Transitions & Re-Arm Cycle

Usage Flow (Compress Device Open Once)

The compress device is opened once at startup and kept open across all WOV cycles.
No pipeline close/reopen is needed between triggers.

Host                        Kernel / ASoC               Firmware
────                        ─────────────               ────────
open comprC0D11         ──► SET_PIPELINE_STATE(RUNNING) ──► VAD: vad_active=false (threshold default=0 → open)
                                                             KPB: KPB_STATE_BUFFERING (shared ring buffer filling, all 3 slots)
                                                             detect_tests: dp_thread running, listening

[optional] write
vad_gate_cfg_100 TLV     ──► MODULE_LARGE_CONFIG_SET  ──► threshold, onset, hangover updated

write wov_init_1NN TLV   ──► MODULE_LARGE_CONFIG_SET  ──► wov_slot_id programmed (slot 0/1/2)
amixer cset wov_mute on  ──► MODULE_LARGE_CONFIG_SET  ──► detection armed for that slot

─────────────────────── Listening (blocking) ───────────────────────────
read() blocks on comprC0D11
arbiter writes silence (zero-fill) into HDA DMA fragments at realtime rate

WOV Trigger Sequence

[voice detected]                                     ──► detect_test auto/real trigger
                        ◄── SOF_IPC4_NOTIFY_PHRASE_DETECTED  (word_id = slot_id)
                        ◄── snd_sof_compr_fragment_elapsed() wakes blocked read()
read() returns PCM ◄────────────────────────────────────────
  (burst: 6 s pre-roll arrives fast, then realtime)

DSP-Initiated Re-Arm (VAD Silence Path)

This is the no-reset re-arm: compress device stays open, pipeline stays RUNNING.

[speech ends, ambient noise below threshold for hangover_frames]
                                                     ──► vad_update_energy(): energy < threshold
                                                         for hangover_frames (default 200 × 10ms = 2s)
                                                         → notifier_event(NOTIFIER_ID_VAD_SILENCE)
                                                         → arb_on_vad_silence():
                                                             active_slot = WOV_ARB_NO_ACTIVE
                                                             broadcast WOV_ARB_CMD_RESUME
                                                         → detect_tests: cd->paused=false, cd->detected=0
                                                         → KPB resumes BUFFERING (shared ring buffer, drain complete)
                                                         → DSP clock → WOVCRO (38.4 MHz)

poll vad_gate_status_100 ────────────────────────────►  MODULE_LARGE_CONFIG_GET(param_id=2)
(TLV ioctl read)                                        returns energy + vad_active=false

Host detects silence, drains remaining compress data
read() blocks again on comprC0D11 ◄───────────────── arbiter back to silence-fill (zero frames)
─────────────────────── Re-armed, listening ─────────────────────────────────

Between-Cycle Re-Arm (Drain + VAD Gate Kcontrol)

The compress device stays RUNNING across all WOV cycles — no compress_stop() /
compress_start() is issued between triggers. The host re-arms by draining and
polling the vad_gate_status_100 kcontrol:

[keyword audio delivered; speech is fading]
                                                     ──► vad_gate energy drops below threshold
                                                         → NOTIFIER_ID_VAD_SILENCE
                                                         → arb_on_vad_silence():
                                                             active_slot = WOV_ARB_NO_ACTIVE
                                                             broadcast WOV_ARB_CMD_RESUME
                                                         → detect_tests resume DP threads

poll vad_gate_status_100 ──► MODULE_LARGE_CONFIG_GET ──► vad_active=false confirmed

[host: drain remaining compress data — read until 600 ms idle]
read() blocks on comprC0D11 ◄───────────────────────── arbiter back to silence-fill
─────────────────────── Re-armed, same compress handle ──────────────────────────

No compress_stop/start between cycles. compress_stop() is issued only when the
application exits (or the pipeline is torn down). Calling compress_stop() between
cycles is unnecessary and resets DMA state, adding latency to the next trigger.

State Transition Summary

State Transition Trigger Compress device
Listening → Active WOV_DETECT detect_test fires Stays OPEN
Active → Listening VAD_SILENCE + drain + kcontrol poll hangover expires, host drains Stays OPEN
Active → Listening STOP/PAUSE pipeline teardown / app exit Closed on compress_close()
Any → closed compress_close() host exits Closed

SOF Notifier Inter-Module Signaling

The SOF Notifier system (src/include/sof/lib/notifier.h) is SOF's intra-DSP
publish/subscribe bus. It works on all platforms (no CONFIG_AMS required) and
is already used for KPB client events. Signals are delivered synchronously to
all registered listeners on the calling core.

Signal Catalog

Notifier ID Direction Payload struct Purpose
NOTIFIER_ID_WOV_DETECT detector → arbiter struct wov_detect_notif { uint8_t slot_id; } Announce keyword detection
NOTIFIER_ID_WOV_CTRL arbiter → all detectors struct wov_ctrl_notif { uint8_t cmd; } Pause/resume detectors
NOTIFIER_ID_VAD_SILENCE vad_gate → arbiter NULL (no payload) Silence hangover expired — re-arm for next trigger

cmd values: WOV_ARB_CMD_PAUSE, WOV_ARB_CMD_RESUME (defined in wov_arbiter.h).

NOTIFIER_ID_VAD_SILENCE is fired by vad_update_energy() when the IIR energy drops
below threshold for hangover_frames consecutive frames. The arbiter's
arb_on_vad_silence() callback handles it: resets active_slot = WOV_ARB_NO_ACTIVE
and broadcasts WOV_ARB_CMD_RESUME to all detectors so they re-arm without any
pipeline RESET or compress device close/reopen.

Full Detect-to-Drain Sequence

sequenceDiagram
    autonumber
    participant DMIC  as DMIC (HW)
    participant KPB   as KPB (shared P100)
    participant DET   as detect_test (slot N)
    participant ARB   as wov_arbiter
    participant HOST  as Host Compress (comprC0D11)
    participant OTHER as detect_test (slots ≠ N)

    Note over DMIC,OTHER: Listening state — shared KPB accumulating pre-roll for all slots

    loop Every 1 ms (LL period)
        DMIC->>KPB: DAI DMA frames
        KPB->>DET: sel_sink copy
    end

    loop Every 20 ms (DP batch)
        DET->>DET: run algorithm on 320-frame batch
    end

    Note over DET,ARB: Keyword detected on slot N

    DET->>HOST: ① IPC4 SOF_IPC4_NOTIFY_PHRASE_DETECTED\n   (word_id = slot_id)
    DET->>KPB: ② notifier_event(WOV_DETECT) [KPB already wired via kpb_client]
    DET->>ARB: ③ notifier_event(NOTIFIER_ID_WOV_DETECT, slot_id=N)

    ARB->>ARB: active_slot = N
    ARB->>OTHER: notifier_event(NOTIFIER_ID_WOV_CTRL, cmd=PAUSE)
    OTHER->>OTHER: cd->paused = true\n(stops DP batching)

    KPB->>ARB: stream pre-roll (up to 6 s) via host_sink
    ARB->>HOST: route slot-N audio to host PCM

    Note over HOST,ARB: Host finishes reading / closes PCM

    HOST->>ARB: trigger STOP (ALSA hw_free / snd_pcm_close)
    ARB->>ARB: active_slot = NO_ACTIVE
    ARB->>OTHER: notifier_event(NOTIFIER_ID_WOV_CTRL, cmd=RESUME)
    ARB->>DET:  notifier_event(NOTIFIER_ID_WOV_CTRL, cmd=RESUME)
    OTHER->>OTHER: cd->paused = false\ncd->detected = 0\nresumed listening
Loading

@lgirdwood
lgirdwood force-pushed the feature/wov-multi-kpb-v2 branch 3 times, most recently from 7240b8d to 653a1b9 Compare August 26, 2026 14:00
Document the multi-slot Wake-on-Voice pipeline architecture, component
interactions, kcontrol interface, tools usage, and pipeline lifecycle.

Sections:
  - Architecture overview: single shared KPB at pipeline 100 feeding
    Mixin → 3×(Mixout → WOV → Arbiter); VAD gate gates the entire chain
  - Signal catalog: SOF notifier events (WOV_DETECT, WOV_CTRL, VAD_SILENCE)
    with payload types and usage notes
  - Arbiter state machine: Idle → Active (keyword detect) → Idle (stream
    close or VAD silence re-arm); DSP-initiated re-arm without pipeline RESET
  - kcontrol reference: complete numid table for vad_gate_cfg/status,
    kpb_cfg, wov_init/mute/trigger_id; amixer command examples
  - Pipeline state transitions: compress device open-once flow, WOV trigger
    sequence, DSP-initiated re-arm (VAD silence path), host-initiated re-arm
  - VAD calibration guide: vad_calibrate.py usage, TGL noise floor table,
    threshold selection for lab vs production
  - Tool usage: wov_daemon.py and wov_capture_app operational guide

KPB topology: documents single-KPB design (192 KB history buffer);
KPB_BUFF_TIME_MS configurable depth (fallback to CONFIG_KPB_MAX_BUFF_TIME).

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Allow multiple downstream WOV detector pipelines to share one KPB
instance by extending the KPB to support a sel_sink drain path alongside
the existing dedicated host_sink path.

Key changes:
- Add sel_sink field to kpb_data to track the downstream WOV detector
  sink; kpb_set_sink() assigns it from the component bind call.
- kpb_init_draining: when host_sink is NULL (multi-KPB WOV topology
  with no dedicated PCM capture), redirect the pre-roll drain through
  sel_sink so that history reaches the wov_arbiter and onward to the
  host copier. Initialise host_period_size from sel_sink stream geometry
  when not already set. Skip pausing the selector component when
  sel_sink is the active drain path.
- kpb_init_draining: cap drain_req to the actual buffered amount instead
  of aborting when less history is available than requested (partial
  pre-roll is better than none).
- kpb_reset: add immediate-reset path when host_sink==NULL in
  BUFFERING/DRAINING state; the LL scheduler is gone after STOP so
  the async-EBUSY path can never complete.
- kpb_init_draining: guard against NULL host_sink to prevent NULL deref
  on WOV-only instances.
- Fix fallback sink assignment in kpb_copy: use && instead of || so the
  fallback only fires when BOTH sel_sink and host_sink are NULL.
- Raise KPB_MAX_BUFF_TIME to 6000 ms; clear HOST_WAKEUP_TIME (no extra
  delay needed with direct sel_sink routing).
- kpb.conf: expose host_sink_index parameter for topology binding.
- Reduce log noise: demote two comp_err to comp_dbg in the RUN copy path.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add three notifier event IDs for the multi-slot WOV arbitration pipeline:

  NOTIFIER_ID_WOV_DETECT  — fired by a keyword detector when a keyword
    is confirmed; payload is struct wov_detect_notif carrying the slot_id
    of the winning detector.  Received by wov_arbiter to activate the
    correct slot and pause all other detectors.

  NOTIFIER_ID_WOV_CTRL  — fired by wov_arbiter to all registered
    detectors; payload is struct wov_ctrl_notif carrying a cmd (PAUSE or
    RESUME) and the active_slot (WOV_ARB_NO_ACTIVE=0xff on RESUME to
    address all slots).

  NOTIFIER_ID_VAD_SILENCE  — fired by vad_gate when the IIR energy stays
    below the configured threshold for hangover_frames consecutive frames
    (sustained silence).  Received by wov_arbiter to reset active_slot and
    broadcast RESUME without a pipeline RESET, enabling multi-cycle capture
    with the compress device held open throughout.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add a lightweight VAD gate component between the DAI copier and the KPB
in the WOV capture pipeline (pipeline 100).  It measures per-frame signal
energy via a first-order IIR estimator and controls downstream pipeline
activity:

Energy gate:
  When smoothed energy falls below the configurable threshold for
  hangover_frames consecutive frames, emit PPL_STATUS_PATH_STOP so the
  mixin (and all downstream KPB/WOV pipelines) can idle.  When energy
  rises above the threshold for onset_frames frames, resume data flow.

Clock scaling:
  WOVCRO (38.4 MHz) during silence; HPRO (~400 MHz) during speech.
  vad_gate_reset() restores WOVCRO so a pipeline RESET during HPRO does
  not leave the clock stuck at high frequency.

S16LE support:
  vad_update_energy() handles both S16LE and S32LE (frame_bytes == 2 or
  4) so the same component works on all platforms.

IPC4 kcontrol interface:
  - vad_gate_cfg_<N> (bytes TLV, R/W): threshold, onset_frames,
    hangover_frames, energy_shift written at pipeline open.
  - vad_gate_status_<N> (bytes TLV, RO): volatile energy + vad_active
    reported via GET_LARGE_CONFIG (param_id=2).

VAD silence notifier:
  When the hangover expires, emit NOTIFIER_ID_VAD_SILENCE so wov_arbiter
  can reset active_slot and re-arm all detectors without a pipeline RESET.

Kconfig: CONFIG_COMP_VAD_GATE (depends on IPC_MAJOR_4).
UUID:    5f6e7d8c-3b4a-1d2c-0e9f-8a7b6c5d4e3f

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add the wov_arbiter audio component which manages keyword detection slot
arbitration for the multi-slot WOV capture pipeline.

Slot arbitration:
  Listens for NOTIFIER_ID_WOV_DETECT from any of up to WOV_ARB_MAX_SLOTS
  (3) detect_test instances.  First-wins: on the first detect event,
  wov_arbiter activates that slot (active_slot = slot_id) and broadcasts
  NOTIFIER_ID_WOV_CTRL PAUSE to all other detectors via the SOF notifier
  bus.  Audio from the active KPB pre-roll + live is forwarded to the
  single host-copier downstream.  Before any keyword fires, the host
  copier sink is filled with silence.

No-reset re-arm:
  Subscribes to NOTIFIER_ID_VAD_SILENCE in wov_arb_prepare(); when
  fired by vad_gate on sustained silence, arb_on_vad_silence() resets
  active_slot to WOV_ARB_NO_ACTIVE and broadcasts WOV_ARB_CMD_RESUME so
  all detectors re-arm.  The compress device stays open throughout — no
  pipeline close/reopen required for multi-cycle capture.

Debug:
  IPC4 SET_LARGE_CONFIG param_id=1 (IPC4_WOV_ARB_SET_ACTIVE_SLOT) forces
  a slot active without a keyword event (lab testing).

Kconfig: CONFIG_COMP_WOV_ARBITER.
UUID:    4a5b6c7d-8e9f-4a1b-2c3d-4e5f60718293

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Extend detect_test (the sample keyword detector) to participate in the
multi-slot WOV arbitration system coordinated by wov_arbiter.

Slot assignment:
  A static wov_slot_id is derived from the pipeline ID at component
  creation time (pipeline 101 → slot 0, 102 → slot 1, 103 → slot 2).
  The slot ID can be overridden via IPC4 LARGE_CONFIG_SET param_id=4
  (IPC4_DETECT_TEST_SET_WOV_SLOT).

DP thread batching:
  Each slot runs a Zephyr k_thread at K_PRIO_PREEMPT(12) with a 4096-byte
  stack.  The LL copy path accumulates 320-frame (20 ms at 16 kHz) S16_LE
  samples into a double-buffer and gives a semaphore when a batch is ready.
  The DP thread wakes, runs the energy detector, and signals the LL thread
  to switch sides.  Slot 2 is pinned to DSP Core 1.  Threads are started
  in prepare() and stopped in reset() and free().

Notifier integration:
  On detection, sends SOF_IPC4_NOTIFY_PHRASE_DETECTED IPC4 notification
  to the host (word_id = wov_slot_id) and fires NOTIFIER_ID_WOV_DETECT
  to wov_arbiter.  Subscribes to NOTIFIER_ID_WOV_CTRL; responds to PAUSE
  (stops detecting) and RESUME (resets cd->detected, resumes).

Per-slot mute switch:
  wov_mute_<N> kcontrol (SOF_IPC4_SWITCH_CONTROL_PARAM_ID): when set to 0
  (muted), test_keyword_copy drains the source buffer and returns without
  running detect_func — keeps LL scheduler running without burning cycles
  for detection.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Enable CONFIG_COMP_WOV_ARBITER, CONFIG_COMP_VAD_GATE, CONFIG_COMP_KPB,
CONFIG_SAMPLES, and CONFIG_SAMPLE_KEYPHRASE on the intel_adsp_cavs25
(TigerLake CAVS2.5) board for default WOV firmware builds.

Also advertise raw PCM as a supported compress-capture codec so the
kernel's ALSA compress core accepts the PCM stream opened on comprC0D11:
  - src/audio/module_adapter/Kconfig: add CONFIG_SOF_COMPRESS_CODEC_PCM_CAP
  - src/audio/base_fw.c: gate SND_COMPRESS_PT_CODEC_ID + codec_id == 0
    accept on SOF_COMPRESS_CODEC_PCM_CAP.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
… capture

Add topology files for the multi-slot Wake-on-Voice pipeline running on
TGL/CAVS2.5 and ARL-S platforms.

Architecture (single shared KPB design):
  DAI copier → VAD Gate → KPB → Mixin → 3×(Mixout → WOV → Arbiter)
                                        → Host copier (compress capture)

  A single shared 192 KB KPB buffer (vs 3 × 192 KB per-slot) serves all
  three detect_test keyword detectors fanned out through the Mixin.

Components added:
  - vad-gate.conf: widget class with bytes kcontrols for runtime threshold
    tuning (vad_gate_cfg_<N> R/W, vad_gate_status_<N> RO energy readback)
  - wov-arbiter.conf: widget class with wov_trigger_id enum kcontrol
    (selects which slot routes audio to the host) and wov_mute switch
  - wov.conf: per-slot wov_mute switch kcontrol (mute/unmute individual
    detect_test instances without a pipeline RESET)
  - host-copier.conf: capture_compatible_d0i3 attribute for D0I3/S0iX
    WOV support while the DSP is in low-power state

Topology manifests:
  - dmic-wov-multi-manifest.conf: HDA DMIC + topology2 manifest for TGL;
    single KPB at pipeline 100, wov_trigger_id for slot routing, D0i3,
    ALSA compress PCM capture (comprC0D11)
  - platform/intel/dmic-wov-multi.conf: DMIC platform file reference
    design (single KPB, same pipeline structure)
  - sof-hda-generic-wov-manifest.conf: full HDA topology with WOV variant

Pipeline support:
  - dai-mixin-be.conf: DAI-to-Mixin pipeline class (extended for inline
    extra widgets like vad-gate)
  - wov-kpb-be.conf: WOV slot pipeline class (Mixout + WOV, no per-slot KPB)
  - wov-detect.conf: WOV detection pipeline wrapper

rimage: tgl.toml.h: add manifest segment for WOV firmware image

Tested: boots and captures on spider (TGL/CAVS2.5), 3-slot WOV with
  multi-cycle re-arm verified.  Single-KPB consolidation requires
  re-test on spider after DUT power-on.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add a bytes kcontrol kpb_cfg_<N> to each KPB widget so the topology
manifest can set the ring-buffer pre-roll duration at load time without
requiring a firmware rebuild.

Firmware (IPC4 path):
  - ipc4/kpb.h: add KP_BUF_CFG_BUFF_TIME_MS = 2 to the param-ID enum
  - kpb.c: add buff_time_ms to comp_data; kpb_get_buff_time_ms() returns
    the topology value when set, otherwise falls back to
    CONFIG_KPB_MAX_BUFF_TIME.  Wire the helper into kpb_params(),
    kpb_prepare() buffer allocation, and drain-request cap.  Handle the
    new LARGE_CONFIG_SET case in kpb_set_large_config().

Topology:
  - kpb.conf: add Object.Control.bytes kpb_cfg_$index with IncludeByKey
    blobs for 6000/4000/2100 ms (sof_abi_hdr + u32 ms = 36 bytes total)
  - dmic-wov-multi-manifest.conf: Define KPB_BUFF_TIME_MS 6000 for
    TGL/CAVS2.5 (6 s pre-roll); omitting the define leaves buff_time_ms=0
    and kpb falls back to CONFIG_KPB_MAX_BUFF_TIME.

The kernel sof_ipc4_widget_kcontrol_setup() sends the blob as a
LARGE_CONFIG_SET on pipeline open — no userspace intervention required.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add three tools for multi-cycle WOV capture testing:

vad_calibrate.py:
  Runtime VAD gate threshold configuration and energy monitoring.
  Reads vad_gate_cfg_100 / vad_gate_status_100 kcontrols (numid=8/9)
  to set the silence threshold and display live IIR energy + vad_active
  state.  Prints a calibration table (noise floor, suggested threshold).

wov_daemon.py:
  Python multi-cycle WOV capture daemon.  Keeps comprC0D11 open across
  all cycles (no pipeline close/reopen per trigger).  Uses per-cycle
  temp files instead of stdout pipe for robustness when the compress
  device is busy.  Polls vad_gate_status_100 to detect silence and drain
  the KPB before re-arming.

wov_capture_app (C daemon):
  Tinycompress-based C application for multi-cycle WOV capture.
  - Holds comprC0D11 open across cycles; compress_wait() drives the
    read/poll loop.
  - Each WOV trigger writes wov_YYYYMMDD_HHMMSS_NNN.wav (S32LE 16kHz mono).
  - Polls vad_gate_status_100 every 200 ms; logs energy/state changes
    with [CTL] prefix and pipeline transitions with [STATE] prefix.
  - Optional -t N flag writes VAD threshold at startup.
  - Build: cd tools/wov_capture && make
  - Deploy: make install DUT=root@spider

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
@lgirdwood
lgirdwood force-pushed the feature/wov-multi-kpb-v2 branch from 653a1b9 to b656712 Compare August 26, 2026 14:54
@lgirdwood

Copy link
Copy Markdown
Member Author

@gkdeepa @naveen-manohar fyi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants