diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 8accb25738a2..7ea3be039d81 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -51,6 +51,57 @@ config HOST_DMA_IPC_POSITION_UPDATES by sending an IPC message whenever one period of audio is transferred. Most platforms provide more efficient ways to query the DMA status. +config STATIC_PIPELINE + bool "Declarative Static Audio Pipeline Loader" + depends on IPC_MAJOR_4 + default n + help + Enable support for declarative static audio pipelines in Sound + Open Firmware. This provides a compile-time and runtime loader + engine for hostless, microcontroller, and standalone embedded + targets that do not rely on dynamic IPC topology messages from + a host operating system. + +config STATIC_PIPELINE_MAX_PIPELINES + int "Maximum static pipelines" + default 16 + depends on STATIC_PIPELINE + help + Maximum number of static audio pipelines tracked by the static + pipeline loader. Memory-constrained embedded devices can lower this. + +config STATIC_PIPELINE_MAX_COMPS + int "Maximum static components" + default 64 + depends on STATIC_PIPELINE + help + Maximum number of static audio components tracked by the static + pipeline loader. + +config STATIC_PIPELINE_MAX_BUFFERS + int "Maximum static audio buffers" + default 64 + depends on STATIC_PIPELINE + help + Maximum number of static intermediate audio buffers tracked by + the static pipeline loader. + +config STATIC_PIPELINE_MAX_CONTROLS + int "Maximum static controls" + default 64 + depends on STATIC_PIPELINE + help + Maximum number of static kcontrols tracked by the static + pipeline loader. + +config STATIC_PIPELINE_MAX_ROUTES + int "Maximum static routes" + default 128 + depends on STATIC_PIPELINE + help + Maximum number of static graph connection routes allowed by + the static pipeline loader. + config COMP_CHAIN_DMA bool "Chain DMA component" depends on IPC_MAJOR_4 diff --git a/src/audio/asrc/asrc.c b/src/audio/asrc/asrc.c index f75009ffd775..69909fc064f7 100644 --- a/src/audio/asrc/asrc.c +++ b/src/audio/asrc/asrc.c @@ -846,4 +846,48 @@ SOF_LLEXT_BUILDINFO; DECLARE_MODULE_ADAPTER(asrc_interface, ASRC_UUID, asrc_tr); SOF_MODULE_INIT(asrc, sys_comp_module_asrc_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +#if CONFIG_IPC_MAJOR_4 +#include "asrc_ipc4.h" + +static struct comp_dev *asrc_static_create(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ + struct ipc4_asrc_module_cfg asrc_cfg; + + memset(&asrc_cfg, 0, sizeof(asrc_cfg)); + sof_static_init_base_cfg(&asrc_cfg.base, cdesc, period_us); + asrc_cfg.out_freq = (enum ipc4_sampling_frequency)(cdesc->caps.sink_rate ? + cdesc->caps.sink_rate : + cdesc->caps.default_rate); + + /* + * Configure ASRC mode bitmask according to stream direction: + * Playback streams operate in PUSH mode (bit 0), while capture streams + * operate in PULL mode (bit 1). This ensures correct endpoint searching + * and buffer size calculation in the ASRC module. + */ + asrc_cfg.asrc_mode = (cdesc->direction == SOF_IPC_STREAM_PLAYBACK) ? + BIT(IPC4_MOD_ASRC_PUSH_MODE) : BIT(IPC4_MOD_ASRC_PULL_MODE); + + struct ipc_config_process spec = { + .size = sizeof(asrc_cfg), + .data = (const uint8_t *)&asrc_cfg, + }; + return drv->ops.create(drv, cfg, &spec); +} + +static struct sof_static_module_ops asrc_static_ops = { + .uuid = &ASRC_UUID, + .create = asrc_static_create, +}; + +DECLARE_STATIC_MODULE_OPS(asrc, &asrc_static_ops); +#endif /* CONFIG_IPC_MAJOR_4 */ +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/component.c b/src/audio/component.c index f798e0a971e0..ff68192018ea 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -100,6 +100,29 @@ int comp_set_adapter_ops(const struct comp_driver *drv, const struct module_inte return -ENODEV; } +const struct comp_driver *comp_driver_find(const struct sof_uuid *uuid, uint32_t type) +{ + struct comp_driver_list *drivers = comp_drivers_get(); + struct list_item *clist; + + if (!drivers) + return NULL; + + list_for_item(clist, &drivers->list) { + struct comp_driver_info *info = container_of(clist, struct comp_driver_info, list); + + /* Match by UUID if provided (used for audio processing modules) */ + if (uuid && info->drv->uid && !memcmp(info->drv->uid, uuid, UUID_SIZE)) + return info->drv; + + /* Match by driver type if no UUID is specified (used for Host and DAI endpoints) */ + if (!uuid && info->drv->type == type) + return info->drv; + } + + return NULL; +} + /* NOTE: Keep the component state diagram up to date: * sof-docs/developer_guides/firmware/components/images/comp-dev-states.pu */ diff --git a/src/audio/drc/drc.c b/src/audio/drc/drc.c index 8709d9abcb4b..ceb1b1b0bd6e 100644 --- a/src/audio/drc/drc.c +++ b/src/audio/drc/drc.c @@ -481,4 +481,37 @@ SOF_LLEXT_BUILDINFO; DECLARE_MODULE_ADAPTER(drc_interface, drc_uuid, drc_tr); SOF_MODULE_INIT(drc, sys_comp_module_drc_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +static int drc_static_apply_switch(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) { + comp_err(dev, "drc_static_apply_switch: NULL processing module"); + return -EINVAL; + } + + struct drc_comp_data *cd = module_get_private_data(mod); + + if (!cd) { + comp_err(dev, "drc_static_apply_switch: NULL private data"); + return -EINVAL; + } + + /* Update switch state and recompute active enabled state immediately */ + cd->enable_switch = (val != 0); + cd->enabled = cd->config && cd->config->params.enabled && cd->enable_switch; + return 0; +} + +static struct sof_static_module_ops drc_static_ops = { + .uuid = &drc_uuid, + .apply_switch = drc_static_apply_switch, +}; + +DECLARE_STATIC_MODULE_OPS(drc, &drc_static_ops); +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/eq_iir/eq_iir.c b/src/audio/eq_iir/eq_iir.c index a9a4bc4aa24a..5451c88e2ad3 100644 --- a/src/audio/eq_iir/eq_iir.c +++ b/src/audio/eq_iir/eq_iir.c @@ -258,4 +258,91 @@ DECLARE_TR_CTX(eq_iir_tr, SOF_UUID(eq_iir_uuid), LOG_LEVEL_INFO); DECLARE_MODULE_ADAPTER(eq_iir_interface, eq_iir_uuid, eq_iir_tr); SOF_MODULE_INIT(eq_iir, sys_comp_module_eq_iir_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +static int eq_iir_static_apply_switch(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) { + comp_err(dev, "eq_iir_static_apply_switch: NULL processing module"); + return -EINVAL; + } + + struct comp_data *cd = module_get_private_data(mod); + + if (!cd) { + comp_err(dev, "eq_iir_static_apply_switch: NULL private data"); + return -EINVAL; + } + + if (val == 0) { + /* Disable EQ by engaging pass-through routine */ + cd->eq_iir_func = eq_iir_pass; + } else if (cd->iir_delay_size) { + /* Re-enable EQ processing matching configured valid bit depth */ + unsigned int valid_bit_depth = mod->priv.cfg.base_cfg.audio_fmt.valid_bit_depth; + + switch (valid_bit_depth) { +#if CONFIG_FORMAT_S16LE + case IPC4_DEPTH_16BIT: + cd->eq_iir_func = eq_iir_s16_default; + break; +#endif +#if CONFIG_FORMAT_S24LE + case IPC4_DEPTH_24BIT: + cd->eq_iir_func = eq_iir_s24_default; + break; +#endif +#if CONFIG_FORMAT_S32LE + case IPC4_DEPTH_32BIT: + cd->eq_iir_func = eq_iir_s32_default; + break; +#endif + default: + /* Fallback inspecting producer stream frame format */ + { + struct comp_buffer *sourceb = comp_dev_get_first_data_producer(dev); + enum sof_ipc_frame fmt = sourceb ? + audio_stream_get_frm_fmt(&sourceb->stream) : + SOF_IPC_FRAME_S32_LE; + + switch (fmt) { +#if CONFIG_FORMAT_S16LE + case SOF_IPC_FRAME_S16_LE: + cd->eq_iir_func = eq_iir_s16_default; + break; +#endif +#if CONFIG_FORMAT_S24LE + case SOF_IPC_FRAME_S24_4LE: + case SOF_IPC_FRAME_S24_3LE: + cd->eq_iir_func = eq_iir_s24_default; + break; +#endif + default: +#if CONFIG_FORMAT_S32LE + cd->eq_iir_func = eq_iir_s32_default; +#elif CONFIG_FORMAT_S16LE + cd->eq_iir_func = eq_iir_s16_default; +#else + cd->eq_iir_func = eq_iir_pass; +#endif + break; + } + } + break; + } + } + return 0; +} + +static struct sof_static_module_ops eq_iir_static_ops = { + .uuid = &eq_iir_uuid, + .apply_switch = eq_iir_static_apply_switch, +}; + +DECLARE_STATIC_MODULE_OPS(eq_iir, &eq_iir_static_ops); +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/level_multiplier/level_multiplier.c b/src/audio/level_multiplier/level_multiplier.c index 4fca482ced72..13945c622898 100644 --- a/src/audio/level_multiplier/level_multiplier.c +++ b/src/audio/level_multiplier/level_multiplier.c @@ -204,4 +204,71 @@ DECLARE_TR_CTX(level_multiplier_tr, SOF_UUID(level_multiplier_uuid), LOG_LEVEL_I DECLARE_MODULE_ADAPTER(level_multiplier_interface, level_multiplier_uuid, level_multiplier_tr); SOF_MODULE_INIT(level_multiplier, sys_comp_module_level_multiplier_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +static int lm_static_apply_volume(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) { + comp_err(dev, "lm_static_apply_volume: NULL processing module"); + return -EINVAL; + } + + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + + if (!cd) { + comp_err(dev, "lm_static_apply_volume: NULL private data"); + return -EINVAL; + } + + /* + * Convert incoming Q1.31 volume (where INT32_MAX represents unity gain 0 dB) + * to the internal Q9.23 format (where LEVEL_MULTIPLIER_GAIN_ONE = 1 << 23). + * We add rounding (+ 128) before shifting right by 8 bits, and clamp + * non-positive values to 0 and values above unity to LEVEL_MULTIPLIER_GAIN_ONE. + */ + if (val <= 0) { + cd->gain = 0; + } else { + int64_t gain_q23 = ((int64_t)val + (1 << 7)) >> 8; + + if (gain_q23 > LEVEL_MULTIPLIER_GAIN_ONE) + gain_q23 = LEVEL_MULTIPLIER_GAIN_ONE; + + cd->gain = (int32_t)gain_q23; + } + return 0; +} + +static int lm_static_apply_switch(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) { + comp_err(dev, "lm_static_apply_switch: NULL processing module"); + return -EINVAL; + } + + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + + if (!cd) { + comp_err(dev, "lm_static_apply_switch: NULL private data"); + return -EINVAL; + } + + cd->gain = (val == 0) ? 0 : LEVEL_MULTIPLIER_GAIN_ONE; + return 0; +} + +static struct sof_static_module_ops lm_static_ops = { + .uuid = &level_multiplier_uuid, + .apply_volume = lm_static_apply_volume, + .apply_switch = lm_static_apply_switch, +}; + +DECLARE_STATIC_MODULE_OPS(level_multiplier, &lm_static_ops); +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/pipeline/CMakeLists.txt b/src/audio/pipeline/CMakeLists.txt index cb26dc3214bd..eca2f06857d0 100644 --- a/src/audio/pipeline/CMakeLists.txt +++ b/src/audio/pipeline/CMakeLists.txt @@ -7,3 +7,9 @@ add_local_sources(sof pipeline-xrun.c pipeline-schedule.c ) + +add_local_sources_ifdef(CONFIG_STATIC_PIPELINE sof + static_pipeline_loader.c + static_pipeline_modules.c + static_pipeline_uac2.c +) diff --git a/src/audio/pipeline/README.md b/src/audio/pipeline/README.md index 4e530bdc1463..ad8aa6d4a27c 100644 --- a/src/audio/pipeline/README.md +++ b/src/audio/pipeline/README.md @@ -240,6 +240,252 @@ sequenceDiagram deactivate Sched ``` +## Static Pipeline Subsystem (Hostless / Standalone Execution) + +In microcontroller, hostless, or standalone embedded environments (e.g. ESP32-P4/S3/C6, Teensy 4.1 / i.MX RT1062, Nordic nRF54, RP2350, audio bridge appliances, smart speakers, standalone DSP dongles), there is no dynamic IPC host (Linux ALSA/SoundWire or Windows driver) to construct audio topologies at runtime. + +The Static Pipeline subsystem (`CONFIG_STATIC_PIPELINE`) provides a declarative C API (``) and loader engine (`static_pipeline_loader.c`) that instantiates full SOF processing graphs directly at boot: + +```mermaid +graph TD + Topo["Declarative Topology Descriptor
(struct sof_static_topology)"] --> Loader["Static Topology Loader
(sof_static_topology_init)"] + Loader --> PPL["Native Pipelines
(pipeline_new)"] + Loader --> COMPS["Components
(DAI, USB/Host, Modules)"] + Loader --> BUFS["Intermediate Buffers
(buffer_alloc)"] + Loader --> ROUTES["Graph Connections
(pipeline_connect)"] + Loader --> CONTROLS["Kcontrols & Custom Callbacks
(Volume, Mute, EQ Bypass, Routing)"] +``` + +### Key Capabilities & Architecture + +1. **Declarative Definitions**: Topologies describe pipelines, components (modules, DAI, USB/Host endpoints), intermediate buffers, routes, PCMs, and kcontrols using builder macros (`SOF_STATIC_MODULE`, `SOF_STATIC_ENDPOINT_DAI`, `SOF_STATIC_ENDPOINT_USB`, `SOF_STATIC_BUFFER`, `SOF_STATIC_ROUTE`, `SOF_STATIC_KCONTROL_*`). +2. **Ops-Driven Module Architecture**: Generic headers and the loader engine are completely decoupled from individual audio modules. Each audio processing component defines and registers operations (`struct sof_static_module_ops`) providing `.create`, `.apply_volume`, `.apply_switch`, and `.apply_enum` callbacks directly in its own module source file via `DECLARE_STATIC_MODULE_OPS()`. +3. **Explicit Parameter Initialization**: Synchronously negotiates stream parameters and prepares all graph components (`comp_params`, `comp_prepare`) to bring non-host-driven pipelines into ready state before audio streaming begins. +4. **Trigger & State Synchronization**: Provides direct runtime pipeline control (`sof_static_pipeline_trigger()`, `sof_static_pipeline_trigger_by_uac2_term()`) with state validation, synchronous trigger execution, per-component trigger propagation, and scheduler copy task activation/cancellation. +5. **Decoupled Control Callbacks**: Standard kcontrols dispatch directly to module ops (Volume, Mute, Level Multiplier, EQ bypass, DRC compression switch, Selector channel), while custom platform or board-level controls (clock mode switching, hardware routing, DMIC injectors) are handled cleanly via `custom_control_handler` callbacks. + +### Usage Guide & Tutorial + +#### 1. Defining Components + +Declare processing modules, host/USB endpoints, and hardware DAIs using either concise constructor macros or explicit struct declarations: + +```c +#include + +extern const struct sof_uuid src_uuid; +extern const struct sof_uuid volume_uuid; +extern const struct sof_uuid eq_iir_uuid; + +static const struct sof_static_comp s_comps[] = { + /* Playback Endpoint (USB UAC2 Terminal ID 1) @ 44.1 kHz */ + SOF_STATIC_ENDPOINT_USB( + 1, 1, "USB_PB", SOF_IPC_STREAM_PLAYBACK, + SOF_IPC_FRAME_S16_LE, 44100, 2, 1 + ), + /* Sample Rate Converter: 44.1 kHz In -> 48 kHz Out */ + SOF_STATIC_MODULE_RATE_CONV( + 2, 1, "SRC_PB", SOF_IPC_STREAM_PLAYBACK, &src_uuid, + SOF_IPC_FRAME_S16_LE, 44100, 48000, 2 + ), + /* Volume Control Module @ 48 kHz */ + SOF_STATIC_MODULE( + 3, 1, "VOL_PB", SOF_IPC_STREAM_PLAYBACK, &volume_uuid, + SOF_IPC_FRAME_S16_LE, 48000, 2, NULL, 0 + ), + /* 4-Band Parametric IIR EQ Module @ 48 kHz */ + SOF_STATIC_MODULE( + 4, 1, "EQ_PB", SOF_IPC_STREAM_PLAYBACK, &eq_iir_uuid, + SOF_IPC_FRAME_S16_LE, 48000, 2, NULL, 0 + ), + /* Hardware I2S DAI Output @ 48 kHz */ + SOF_STATIC_ENDPOINT_DAI( + 5, 1, "I2S_TX", SOF_IPC_STREAM_PLAYBACK, + SOF_IPC_FRAME_S16_LE, 48000, 2, + SOF_DAI_INTEL_SSP, 0, SOF_DAI_FMT_I2S | SOF_DAI_FMT_CBC_CFC + ), +}; +``` + +#### 2. Defining Intermediate Buffers + +Allocate audio buffers connecting consecutive processing blocks. In multi-rate pipelines (such as with SRC / ASRC), buffers can declare their individual sample rates and channel counts or inherit them dynamically from the upstream producer: + +```c +static const struct sof_static_buffer s_buffers[] = { + /* Buffer between USB_PB (1) and SRC_PB (2) @ 44.1 kHz */ + SOF_STATIC_BUFFER( + .id = 1, + .size = 45 * 2 * sizeof(int16_t) * 4, /* 4 periods @ 44.1 kHz */ + .fmt = SOF_IPC_FRAME_S16_LE, + .rate = 44100, + .channels = 2, + ), + /* Buffer between SRC_PB (2) and VOL_PB (3) @ 48 kHz */ + SOF_STATIC_BUFFER( + .id = 2, + .size = 48 * 2 * sizeof(int16_t) * 4, /* 4 periods @ 48 kHz */ + .fmt = SOF_IPC_FRAME_S16_LE, + .rate = 48000, + .channels = 2, + ), + /* Buffer between VOL_PB (3) and EQ_PB (4) @ 48 kHz */ + SOF_STATIC_BUFFER( + .id = 3, + .size = 48 * 2 * sizeof(int16_t) * 4, + .fmt = SOF_IPC_FRAME_S16_LE, + .rate = 48000, + .channels = 2, + ), + /* Buffer between EQ_PB (4) and I2S_TX (5) @ 48 kHz */ + SOF_STATIC_BUFFER( + .id = 4, + .size = 48 * 2 * sizeof(int16_t) * 4, + .fmt = SOF_IPC_FRAME_S16_LE, + .rate = 48000, + .channels = 2, + ), +}; +``` + +#### 3. Defining Graph Routes + +Connect component outputs through intermediate buffers to component inputs: + +```c +static const struct sof_static_route s_routes[] = { + SOF_STATIC_ROUTE(.src_comp_id = 1, .buffer_id = 1, .sink_comp_id = 2), + SOF_STATIC_ROUTE(.src_comp_id = 2, .buffer_id = 2, .sink_comp_id = 3), + SOF_STATIC_ROUTE(.src_comp_id = 3, .buffer_id = 3, .sink_comp_id = 4), + SOF_STATIC_ROUTE(.src_comp_id = 4, .buffer_id = 4, .sink_comp_id = 5), +}; +``` + +#### 4. Defining Top-Level Pipelines + +Specify pipeline execution properties, scheduling domains, and boundary endpoints: + +```c +static const struct sof_static_pipeline_desc s_pipelines[] = { + { + .pipeline_id = 1, + .name = "Playback Pipeline", + .direction = SOF_IPC_STREAM_PLAYBACK, + .priority = 0, + .core = 0, + .period = 1000, /* 1 ms tick */ + .frames_per_sched = 48, /* 48 frames per ms @ 48 kHz */ + .time_domain = SOF_TIME_DOMAIN_TIMER, + .default_rate = 48000, /* Pipeline target rate */ + .default_channels = 2, + .sched_comp_id = 5, /* Driven by I2S DAI */ + .source_comp_id = 1, /* Ingress: USB_PB */ + .sink_comp_id = 5, /* Egress: I2S_TX */ + }, +}; +``` + +#### 5. Defining Kcontrols & Custom Handlers + +Declare runtime controls (volume, mute, filter bypass switches) and optional platform callbacks: + +```c +enum { + CTRL_ID_VOLUME = 1, + CTRL_ID_EQ_BYPASS = 2, + CTRL_ID_CLOCK_MODE = 3, +}; + +static const struct sof_static_kcontrol s_controls[] = { + SOF_STATIC_KCONTROL_VOLUME( + .id = CTRL_ID_VOLUME, + .name = "Master Playback Volume", + .target_comp_id = 3, /* Targets VOL_PB (3) */ + .min = 0, + .max = INT32_MAX, + .def = INT32_MAX, /* 0 dB unity gain default (Q1.31) */ + .channels = 2, + .uac2_entity_id = 10, /* Bound to UAC2 Feature Unit 10 */ + ), + SOF_STATIC_KCONTROL_SWITCH( + .id = CTRL_ID_EQ_BYPASS, + .name = "EQ Bypass Switch", + .target_comp_id = 4, /* Targets EQ_PB (4, 0=bypass, 1=active) */ + .min = 0, + .max = 1, + .def = 1, + .channels = 1, + ), + SOF_STATIC_KCONTROL_SWITCH( + .id = CTRL_ID_CLOCK_MODE, + .name = "Hardware Clock Mode", + .target_comp_id = 0, /* Custom control (dispatched to callback) */ + .min = 0, + .max = 1, + .def = 1, /* 1 = Master, 0 = Slave */ + .channels = 1, + ), +}; + +static int platform_control_callback(const struct sof_static_kcontrol *ctl, + int32_t val, void *priv) +{ + if (ctl->id == CTRL_ID_CLOCK_MODE) { + /* Reconfigure physical I2S clock mode between Master and Slave */ + return platform_set_i2s_clock_mode(val == 1); + } + return -EINVAL; +} + +const struct sof_static_topology g_my_platform_topology = { + .name = "Demo Playback Topology", + .num_pipelines = ARRAY_SIZE(s_pipelines), + .pipelines = s_pipelines, + .num_comps = ARRAY_SIZE(s_comps), + .comps = s_comps, + .num_buffers = ARRAY_SIZE(s_buffers), + .buffers = s_buffers, + .num_routes = ARRAY_SIZE(s_routes), + .routes = s_routes, + .num_controls = ARRAY_SIZE(s_controls), + .controls = s_controls, + .custom_control_handler = platform_control_callback, + .custom_control_data = NULL, +}; +``` + +#### 6. Instantiating and Running the Topology + +At board startup, call `sof_static_topology_init()`: + +```c +#include + +int my_platform_init(void) +{ + /* Build and prepare audio pipeline graph */ + int ret = sof_static_topology_init(&g_my_platform_topology); + if (ret < 0) { + LOG_ERR("Failed to initialize static topology: %d", ret); + return ret; + } + + /* Start audio streaming */ + sof_static_pipeline_trigger(1, true); + return 0; +} +``` + +#### 7. Runtime Control APIs + +- **`sof_static_kcontrol_set(uint32_t ctrl_id, int32_t val)`**: Updates volume, mute, or bypass state at runtime. +- **`sof_static_kcontrol_get(uint32_t ctrl_id, int32_t *val)`**: Reads current control value. +- **`sof_static_pipeline_start(uint32_t pipeline_id)`**: Prepares and starts a pipeline synchronously. +- **`sof_static_pipeline_stop(uint32_t pipeline_id)`**: Stops and pauses an active pipeline synchronously. +- **`sof_static_pipeline_trigger(uint32_t pipeline_id, bool start)`**: Starts or stops a pipeline synchronously. +- **`sof_static_kcontrol_set_by_uac2(uint8_t entity_id, uint8_t ch, int32_t val, bool is_volume)`**: Automatically translates USB UAC2 8.8 dB fader commands to SOF native volume values. + ## Configuration and Scripts -* **CMakeLists.txt**: Straightforward build configuration integrating the fundamental internal execution blocks of the SOF graph: `pipeline-graph.c`, `pipeline-stream.c`, `pipeline-params.c`, `pipeline-xrun.c`, and `pipeline-schedule.c`. +* **CMakeLists.txt**: Build configuration integrating internal execution blocks of the SOF graph (`pipeline-graph.c`, `pipeline-stream.c`, `pipeline-params.c`, `pipeline-xrun.c`, `pipeline-schedule.c`) and conditionally compiling `static_pipeline_loader.c`, `static_pipeline_modules.c`, and `static_pipeline_uac2.c` when `CONFIG_STATIC_PIPELINE=y`. +* **Endpoint Callbacks**: Hardware DAI endpoints can attach optional platform-specific clock and hardware configuration callbacks via `SOF_STATIC_ENDPOINT_DAI_CFG()` without polluting the generic loader with SoC-specific code. diff --git a/src/audio/pipeline/static_pipeline_loader.c b/src/audio/pipeline/static_pipeline_loader.c new file mode 100644 index 000000000000..ace6eef3cb9a --- /dev/null +++ b/src/audio/pipeline/static_pipeline_loader.c @@ -0,0 +1,1873 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +/** + * \file audio/pipeline/static_pipeline_loader.c + * \brief Generic Static Audio Topology Loader Engine + * \author Liam Girdwood + * + * Implements the core runtime engine for declarative static audio pipelines. + * In microcontroller, hostless, and standalone embedded environments, this engine + * instantiates SOF components, buffers, routes, and kcontrols directly at boot + * without dynamic IPC topology commands from a host operating system. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../copier/copier.h" +#include "static_pipeline_modules.h" + +LOG_MODULE_REGISTER(static_pipeline_loader, CONFIG_SOF_LOG_LEVEL); + +/* + * Capacity limits for static topology tracking tables. + * Sourced from Kconfig to allow memory-constrained microcontrollers + * and embedded DSPs to scale tracking buffers appropriately. + */ +#ifndef CONFIG_STATIC_PIPELINE_MAX_PIPELINES +#define CONFIG_STATIC_PIPELINE_MAX_PIPELINES 16 +#endif +#ifndef CONFIG_STATIC_PIPELINE_MAX_COMPS +#define CONFIG_STATIC_PIPELINE_MAX_COMPS 64 +#endif +#ifndef CONFIG_STATIC_PIPELINE_MAX_BUFFERS +#define CONFIG_STATIC_PIPELINE_MAX_BUFFERS 64 +#endif +#ifndef CONFIG_STATIC_PIPELINE_MAX_CONTROLS +#define CONFIG_STATIC_PIPELINE_MAX_CONTROLS 64 +#endif +#ifndef CONFIG_STATIC_PIPELINE_MAX_ROUTES +#define CONFIG_STATIC_PIPELINE_MAX_ROUTES 128 +#endif + +#define MAX_STATIC_PIPELINES CONFIG_STATIC_PIPELINE_MAX_PIPELINES +#define MAX_STATIC_COMPS CONFIG_STATIC_PIPELINE_MAX_COMPS +#define MAX_STATIC_BUFFERS CONFIG_STATIC_PIPELINE_MAX_BUFFERS +#define MAX_STATIC_CONTROLS CONFIG_STATIC_PIPELINE_MAX_CONTROLS +#define MAX_STATIC_ROUTES CONFIG_STATIC_PIPELINE_MAX_ROUTES + +/* Active declarative topology descriptor */ +static APP_SYSUSER_DATA const struct sof_static_topology *s_active_topo; + +/* Static tracking tables for fast ID-to-pointer lookups */ +static APP_SYSUSER_BSS struct pipeline *s_pipelines[MAX_STATIC_PIPELINES]; +static APP_SYSUSER_BSS uint32_t s_pipeline_ids[MAX_STATIC_PIPELINES]; +static APP_SYSUSER_BSS uint32_t s_pipeline_rates[MAX_STATIC_PIPELINES]; +static APP_SYSUSER_BSS size_t s_num_pipelines; + +static APP_SYSUSER_BSS struct comp_dev *s_comps[MAX_STATIC_COMPS]; +static APP_SYSUSER_BSS uint32_t s_comp_ids[MAX_STATIC_COMPS]; +static APP_SYSUSER_BSS size_t s_num_comps; + +static APP_SYSUSER_BSS struct comp_buffer *s_buffers[MAX_STATIC_BUFFERS]; +static APP_SYSUSER_BSS uint32_t s_buffer_ids[MAX_STATIC_BUFFERS]; +static APP_SYSUSER_BSS size_t s_num_buffers; + +/* Cached kcontrol values */ +static APP_SYSUSER_BSS int32_t s_control_vals[MAX_STATIC_CONTROLS]; + +/** + * \brief Calculate valid sample byte size for a given PCM frame format. + * + * Handles special case SOF_IPC_FRAME_S16_4LE where 16 valid bits reside in a + * 32-bit container, returning 2 valid bytes rather than container size 4. + * + * \param[in] fmt PCM frame format enum. + * \return Valid sample bytes (e.g. 2 for S16/S16_4LE, 3 for S24_3LE, 4 for S24_4LE/S32/FLOAT). + */ +static inline uint32_t static_get_sample_valid_bytes(enum sof_ipc_frame fmt) +{ + if (fmt == SOF_IPC_FRAME_S16_4LE) + return 2; + return get_sample_bitdepth(fmt) / 8; +} + +/** + * \brief Convert sample rate in Hz to capability bitmask. + * \param[in] rate Sample rate in Hz. + * \return Matching SOF_STATIC_RATE_* bitmask or 0. + */ +static inline uint32_t sof_static_rate_to_mask(uint32_t rate) +{ + /* Map standard audio sampling rates to static capability bitmasks */ + switch (rate) { + case 8000: return SOF_STATIC_RATE_8K; + case 11025: return SOF_STATIC_RATE_11K; + case 12000: return SOF_STATIC_RATE_12K; + case 16000: return SOF_STATIC_RATE_16K; + case 22050: return SOF_STATIC_RATE_22K; + case 24000: return SOF_STATIC_RATE_24K; + case 32000: return SOF_STATIC_RATE_32K; + case 44100: return SOF_STATIC_RATE_44K1; + case 48000: return SOF_STATIC_RATE_48K; + case 64000: return SOF_STATIC_RATE_64K; + case 88200: return SOF_STATIC_RATE_88K2; + case 96000: return SOF_STATIC_RATE_96K; + case 176400: return SOF_STATIC_RATE_176K4; + case 192000: return SOF_STATIC_RATE_192K; + case 384000: return SOF_STATIC_RATE_384K; + default: return 0; + } +} + +/** + * \brief Helper to find declarative component descriptor by unique component ID. + * \param[in] topo Active topology pointer. + * \param[in] comp_id Target component identifier. + * \return Pointer to matched struct sof_static_comp or NULL. + */ +static const struct sof_static_comp * +sof_static_find_comp_desc(const struct sof_static_topology *topo, uint32_t comp_id) +{ + if (!topo) + return NULL; + + /* Linear search across declared components */ + for (size_t c = 0; c < topo->num_comps; c++) { + if (topo->comps[c].id == comp_id) + return &topo->comps[c]; + } + return NULL; +} + +/** + * \brief Helper to find declarative pipeline descriptor by unique pipeline ID. + * \param[in] topo Active topology pointer. + * \param[in] pipeline_id Target pipeline identifier. + * \return Pointer to matched struct sof_static_pipeline_desc or NULL. + */ +static const struct sof_static_pipeline_desc * +sof_static_find_pipeline_desc(const struct sof_static_topology *topo, uint32_t pipeline_id) +{ + if (!topo) + return NULL; + + for (size_t p = 0; p < topo->num_pipelines; p++) { + if (topo->pipelines[p].pipeline_id == pipeline_id) + return &topo->pipelines[p]; + } + return NULL; +} + +/** + * \brief Send initial binary configuration blob (e.g. EQ coefficients, DRC tuning) to a component. + * + * In hostless environments, modules requiring configuration tables (such as equalizer + * filter coefficients or DRC curves) must be configured directly at boot. This routine + * dispatches the initial configuration blob via the IPC4 module adapter's set_configuration ops. + * + * \param[in] dev Target component device pointer. + * \param[in] abi_blob Pointer to ABI configuration blob starting with struct sof_abi_hdr. + * \param[in] abi_blob_total_size Total size of blob in bytes including header. + * \return 0 on success, negative errno on failure. + */ +static int send_comp_config(struct comp_dev *dev, const void *abi_blob, size_t abi_blob_total_size) +{ + /* Validate component pointer and verify minimum blob size covers ABI header */ + if (!dev || !abi_blob || abi_blob_total_size < sizeof(struct sof_abi_hdr)) { + LOG_ERR("send_comp_config: invalid parameter or truncated blob (size %zu)", + abi_blob_total_size); + return -EINVAL; + } + + const struct sof_abi_hdr *hdr = (const struct sof_abi_hdr *)abi_blob; + + /* Verify payload length fits within supplied total size */ + if (hdr->size > abi_blob_total_size - sizeof(struct sof_abi_hdr)) { + LOG_ERR("send_comp_config: payload size %u exceeds total size %zu", + hdr->size, abi_blob_total_size); + return -EINVAL; + } + + struct processing_module *mod = comp_mod(dev); + + /* Verify the component's module adapter driver provides set_configuration */ + if (!mod || !mod->dev || !mod->dev->drv || !mod->dev->drv->adapter_ops || + !mod->dev->drv->adapter_ops->set_configuration) { + LOG_ERR("No adapter_ops set_configuration for comp %d", dev->ipc_config.id); + return -EINVAL; + } + + /* Pass raw configuration payload directly following ABI header to module adapter */ + const uint8_t *raw_data = (const uint8_t *)abi_blob + sizeof(struct sof_abi_hdr); + int ret = mod->dev->drv->adapter_ops->set_configuration(mod, 0, MODULE_CFG_FRAGMENT_SINGLE, + hdr->size, raw_data, + hdr->size, NULL, 0); + + /* Log configuration status for diagnostics */ + if (ret < 0) + LOG_ERR("set_configuration failed for comp %d: ret %d", dev->ipc_config.id, ret); + else + LOG_INF("Configuration blob loaded for comp %d (%u bytes)", dev->ipc_config.id, hdr->size); + + return ret; +} + +/** + * \brief Initialize audio stream and buffer parameters for an intermediate connection buffer. + * + * Configures the IPC stream parameters (sampling rate, channel count, frame format, + * container sizes, valid bit depth, host period bytes, and ALSA channel map) and + * synchronizes the underlying audio stream (buf->stream) as well as its associated + * sink and source interfaces. + * + * Intermediate circular buffers connect adjacent processing components in the + * graph. Initializing their stream parameters and reset state ensures that + * buffer_set_params() and sink/source getters establish identical format + * expectations prior to component parameter negotiation and streaming. + * + * \param[in,out] buf Pointer to allocated intermediate component buffer. + * \param[in] dir Audio stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param[in] fmt PCM audio frame format (enum sof_ipc_frame). + * \param[in] rate Audio sampling rate in Hz. + * \param[in] channels Audio channel count. + */ +static void init_buffer_params(struct comp_buffer *buf, uint32_t dir, enum sof_ipc_frame fmt, + uint32_t rate, uint32_t channels) +{ + if (!buf) + return; + + /* Calculate sample container and valid byte sizes based on the PCM format */ + uint32_t sample_bytes = get_sample_bytes(fmt); + uint32_t valid_bytes = static_get_sample_valid_bytes(fmt); + + /* Number of audio frames per 1 millisecond scheduling period */ + uint32_t frames = rate / 1000; + + /* Populate standard IPC stream parameters structure */ + struct sof_ipc_stream_params params; + memset(¶ms, 0, sizeof(params)); + params.rate = rate; + params.channels = channels; + params.frame_fmt = fmt; + params.sample_container_bytes = sample_bytes; + params.sample_valid_bytes = valid_bytes; + params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + params.host_period_bytes = frames * channels * sample_bytes; + params.direction = dir; + + /* Populate standard channel mapping for mono, stereo, and multi-channel streams */ + for (uint32_t c = 0; c < SOF_IPC_MAX_CHANNELS; c++) { + if (c == 0) + params.chmap[c] = (channels == 1) ? SOF_CHMAP_MONO : SOF_CHMAP_FL; + else if (c == 1 && channels > 1) + params.chmap[c] = SOF_CHMAP_FR; + else if (c < channels) + params.chmap[c] = c + 1; + else + params.chmap[c] = SOF_CHMAP_NA; + } + + /* Apply stream parameters to buffer structure */ + buffer_set_params(buf, ¶ms, BUFFER_UPDATE_FORCE); + + /* Configure circular audio stream parameters and byte alignment */ + audio_stream_set_valid_fmt(&buf->stream, fmt); + audio_stream_set_rate(&buf->stream, rate); + audio_stream_set_channels(&buf->stream, channels); + audio_stream_set_frm_fmt(&buf->stream, fmt); + audio_stream_set_align(SOF_FRAME_BYTE_ALIGN, sample_bytes, &buf->stream); + audio_stream_reset(&buf->stream); + + /* Synchronize sink interface parameters so downstream consumers match */ + struct sof_sink *sink = audio_buffer_get_sink(&buf->audio_buffer); + if (sink) { + sink_set_valid_fmt(sink, fmt); + sink_set_rate(sink, rate); + sink_set_channels(sink, channels); + sink_set_frm_fmt(sink, fmt); + sink_set_alignment_constants(sink, SOF_FRAME_BYTE_ALIGN, sample_bytes); + } + + /* Synchronize source interface parameters so upstream producers match */ + struct sof_source *source = audio_buffer_get_source(&buf->audio_buffer); + if (source) { + source_set_valid_fmt(source, fmt); + source_set_rate(source, rate); + source_set_channels(source, channels); + source_set_frm_fmt(source, fmt); + source_set_alignment_constants(source, SOF_FRAME_BYTE_ALIGN, sample_bytes); + } +} + +/** + * \brief Stage 1: Allocate top-level pipelines. + * + * Allocate pipeline objects first because components and buffers require an + * owning pipeline context for scheduling, buffer allocation, and clock domains. + * Each pipeline maintains its own sampling rate, scheduling period, and time domain. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_init_pipelines(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_pipelines && i < MAX_STATIC_PIPELINES; i++) { + const struct sof_static_pipeline_desc *pdesc = &topo->pipelines[i]; + struct pipeline *pipe = pipeline_new(NULL, pdesc->pipeline_id, pdesc->priority, + pdesc->pipeline_id, NULL); + if (!pipe) { + LOG_ERR("Failed to allocate pipeline %u (%s)", pdesc->pipeline_id, pdesc->name); + return -ENOMEM; + } + + /* Configure pipeline scheduling parameters from declarative descriptor */ + pipe->pipeline_id = pdesc->pipeline_id; + pipe->core = pdesc->core; + pipe->period = pdesc->period ? pdesc->period : 1000; + pipe->frames_per_sched = pdesc->frames_per_sched ? pdesc->frames_per_sched : 48; + pipe->time_domain = pdesc->time_domain; + + /* Record pipeline instance and sample rate in static tracking arrays */ + s_pipelines[s_num_pipelines] = pipe; + s_pipeline_ids[s_num_pipelines] = pdesc->pipeline_id; + s_pipeline_rates[s_num_pipelines] = pdesc->default_rate ? pdesc->default_rate : 48000; + s_num_pipelines++; + + LOG_INF("Pipeline %u ('%s') initialized (period %u us, %u frames, %u Hz)", + pdesc->pipeline_id, pdesc->name ? pdesc->name : "", pipe->period, + pipe->frames_per_sched, s_pipeline_rates[s_num_pipelines - 1]); + } + + return 0; +} + +/** + * \brief Instantiate a hardware DAI endpoint component. + * + * \param[in] cdesc Component descriptor. + * \param[in] drv Component driver pointer. + * \param[in] cfg Standard component IPC configuration. + * \return Pointer to created comp_dev, or NULL on failure. + */ +static struct comp_dev *sof_static_init_dai_comp(const struct sof_static_comp *cdesc, + const struct comp_driver *drv, + const struct comp_ipc_config *cfg, + const struct sof_static_topology *topo) +{ + /* Validate component and driver descriptor pointers */ + if (!cdesc || !drv || !cfg) { + LOG_ERR("sof_static_init_dai_comp: invalid NULL arguments"); + return NULL; + } + + const struct sof_static_pipeline_desc *pdesc = + sof_static_find_pipeline_desc(topo, cdesc->pipeline_id); + + /* Derive DAI sampling frequency from component caps or owning pipeline rate */ + uint32_t dai_rate = cdesc->caps.default_rate; + if (!dai_rate && pdesc) + dai_rate = pdesc->default_rate; + if (!dai_rate) + dai_rate = 48000; + + struct ipc_config_dai dai_cfg = { + .type = cdesc->ep.dai.dai_type, + .dai_index = cdesc->ep.dai.dai_index, + .direction = cdesc->direction, + .sampling_frequency = dai_rate, + .dma_buffer_size = 1024, + .format = cdesc->ep.dai.format, + }; + struct comp_dev *dev = drv->ops.create(drv, cfg, &dai_cfg); + + if (!dev) { + LOG_ERR("Failed to create DAI component %u ('%s')", cdesc->id, cdesc->name); + return NULL; + } + + /* Synthesize IPC4 copier and gateway configuration expected by IPC4 DAI driver */ + struct ipc4_copier_module_cfg copier_cfg; + memset(&copier_cfg, 0, sizeof(copier_cfg)); + sof_static_init_base_cfg(&copier_cfg.base, cdesc, pdesc ? pdesc->period : 1000); + copier_cfg.out_fmt = copier_cfg.base.audio_fmt; + + enum ipc4_connector_node_id_type node_type; + switch (cdesc->ep.dai.dai_type) { + case SOF_DAI_INTEL_SSP: + node_type = (cdesc->direction == SOF_IPC_STREAM_PLAYBACK) ? + ipc4_i2s_link_output_class : ipc4_i2s_link_input_class; + break; + case SOF_DAI_INTEL_DMIC: + node_type = ipc4_dmic_link_input_class; + break; + case SOF_DAI_INTEL_HDA: + node_type = (cdesc->direction == SOF_IPC_STREAM_PLAYBACK) ? + ipc4_hda_link_output_class : ipc4_hda_link_input_class; + break; + case SOF_DAI_INTEL_ALH: + node_type = (cdesc->direction == SOF_IPC_STREAM_PLAYBACK) ? + ipc4_alh_link_output_class : ipc4_alh_link_input_class; + break; + default: + node_type = (cdesc->direction == SOF_IPC_STREAM_PLAYBACK) ? + ipc4_i2s_link_output_class : ipc4_i2s_link_input_class; + break; + } + + copier_cfg.gtw_cfg.node_id.f.dma_type = node_type; + copier_cfg.gtw_cfg.node_id.f.v_index = cdesc->ep.dai.dai_index; + copier_cfg.gtw_cfg.dma_buffer_size = 1024; + copier_cfg.gtw_cfg.config_length = 0; + + /* Invoke optional platform/endpoint-specific configuration callback */ + if (cdesc->ep.dai.configure) { + int ret_cfg = cdesc->ep.dai.configure(dev, &dai_cfg, &copier_cfg); + if (ret_cfg < 0) { + LOG_ERR("DAI endpoint configure callback failed %d for comp %u", + ret_cfg, cdesc->id); + return NULL; + } + } + + /* Apply DAI configuration to driver data via the component driver framework */ + struct dai_data *dd = comp_get_drvdata(dev); + + if (dd) { + int ret_dai = comp_dai_config(dd, dev, &dai_cfg, &copier_cfg); + if (ret_dai < 0) { + LOG_ERR("comp_dai_config failed %d for comp %u", ret_dai, cdesc->id); + return NULL; + } + } + + return dev; +} + +/** + * \brief Instantiate a host/USB endpoint component. + * + * Populates struct ipc_config_host expected by the host component driver + * (comp_host::create / host_new) rather than using module adapter fallback. + * + * \param[in] cdesc Component descriptor. + * \param[in] drv Component driver pointer. + * \param[in] cfg Standard component IPC configuration. + * \return Pointer to created comp_dev, or NULL on failure. + */ +static struct comp_dev *sof_static_init_host_comp(const struct sof_static_comp *cdesc, + const struct comp_driver *drv, + const struct comp_ipc_config *cfg) +{ + /* Validate component and driver descriptor pointers */ + if (!cdesc || !drv || !cfg) { + LOG_ERR("sof_static_init_host_comp: invalid NULL arguments"); + return NULL; + } + + /* Populate host driver configuration */ + struct ipc_config_host host_cfg = { + .direction = cdesc->direction, + .no_irq = 0, + .dmac_config = 0, + .dma_buffer_size = 1024, + .feature_mask = 0, + }; + + /* Invoke host driver create callback */ + struct comp_dev *dev = drv->ops.create(drv, cfg, &host_cfg); + if (!dev) { + LOG_ERR("Failed to create host component %u ('%s')", cdesc->id, cdesc->name); + return NULL; + } + + return dev; +} + +/** + * \brief Instantiate an audio processing module or host endpoint component. + * + * \param[in] cdesc Component descriptor. + * \param[in] drv Component driver pointer. + * \param[in] cfg Standard component IPC configuration. + * \param[in] topo Topology descriptor pointer. + * \return Pointer to created comp_dev, or NULL on failure. + */ +static struct comp_dev *sof_static_init_module_comp(const struct sof_static_comp *cdesc, + const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_topology *topo) +{ + const struct sof_static_pipeline_desc *pdesc = NULL; + + for (size_t p = 0; p < topo->num_pipelines; p++) { + if (topo->pipelines[p].pipeline_id == cdesc->pipeline_id) { + pdesc = &topo->pipelines[p]; + break; + } + } + uint32_t period_us = (pdesc && pdesc->period) ? pdesc->period : 1000; + + /* Delegate instantiation to registered static ops or generic fallback */ + const struct sof_static_module_ops *ops = sof_static_find_module_ops(cdesc->uuid); + + if (ops && ops->create) + return ops->create(drv, cfg, cdesc, period_us); + + return sof_static_module_create_default(drv, cfg, cdesc, period_us); +} + +/** + * \brief Stage 2: Instantiate components. + * + * Create all declared audio processing modules, DAI endpoints, and host/USB + * interfaces. Look up drivers in the global SOF registry and initialize them. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_init_comps(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_comps && i < MAX_STATIC_COMPS; i++) { + const struct sof_static_comp *cdesc = &topo->comps[i]; + uint32_t drv_type = (cdesc->type == SOF_STATIC_COMP_HOST) ? SOF_COMP_HOST : + (cdesc->type == SOF_STATIC_COMP_DAI) ? SOF_COMP_DAI : + SOF_COMP_MODULE_ADAPTER; + + /* Look up driver from component driver registry using standard API */ + const struct comp_driver *drv = comp_driver_find(cdesc->uuid, drv_type); + if (!drv) { + LOG_ERR("Component driver not found for comp %u ('%s')", cdesc->id, cdesc->name); + return -ENODEV; + } + + /* Find owning pipeline descriptor to resolve core affinity and scheduling */ + const struct sof_static_pipeline_desc *pdesc = + sof_static_find_pipeline_desc(topo, cdesc->pipeline_id); + uint32_t comp_core = pdesc ? pdesc->core : 0; + + /* Populate standard SOF component IPC configuration */ + struct comp_ipc_config cfg = { + .id = cdesc->id, + .pipeline_id = cdesc->pipeline_id, + .core = comp_core, + .proc_domain = COMP_PROCESSING_DOMAIN_LL, + .frame_fmt = cdesc->caps.default_fmt, + .type = drv_type, + }; + + struct comp_dev *dev; + + if (cdesc->type == SOF_STATIC_COMP_DAI) + dev = sof_static_init_dai_comp(cdesc, drv, &cfg, topo); + else if (cdesc->type == SOF_STATIC_COMP_HOST) + dev = sof_static_init_host_comp(cdesc, drv, &cfg); + else + dev = sof_static_init_module_comp(cdesc, drv, &cfg, topo); + + if (!dev) { + LOG_ERR("Failed to create component %u ('%s')", cdesc->id, cdesc->name); + return -ENOMEM; + } + + /* Set component stream direction and owning pipeline */ + dev->direction = cdesc->direction; + dev->pipeline = sof_static_pipeline_get(cdesc->pipeline_id); + + /* Assign scheduling period from owning pipeline descriptor */ + dev->period = (pdesc && pdesc->period) ? pdesc->period : 1000; + + /* Derive component frames per scheduling period */ + uint32_t comp_rate = cdesc->caps.default_rate ? cdesc->caps.default_rate : + ((pdesc && pdesc->default_rate) ? pdesc->default_rate : + 48000); + dev->frames = (comp_rate * dev->period) / 1000000; + if (dev->frames == 0) + dev->frames = 48; + + /* Send initial configuration blob if specified (EQ, DRC, etc.) */ + if (cdesc->init_blob && cdesc->init_blob_size > 0) { + int ret_blob = send_comp_config(dev, cdesc->init_blob, cdesc->init_blob_size); + if (ret_blob < 0) { + LOG_ERR("Failed to send init blob for component %u: %d", cdesc->id, ret_blob); + return ret_blob; + } + } + + /* Record component instance in static tracking array */ + s_comps[s_num_comps] = dev; + s_comp_ids[s_num_comps] = cdesc->id; + s_num_comps++; + + LOG_INF("Component %u ('%s') created in pipeline %u", cdesc->id, cdesc->name, cdesc->pipeline_id); + } + + return 0; +} + +/** + * \brief Stage 3: Allocate audio buffers. + * + * Allocate intermediate circular buffers linking components. Each buffer inherits + * its rate, channels, format, and stream direction from its connected producer component. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_init_buffers(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_buffers && i < MAX_STATIC_BUFFERS; i++) { + const struct sof_static_buffer *bdesc = &topo->buffers[i]; + struct comp_buffer *buf = buffer_alloc(NULL, bdesc->size, bdesc->flags, + PLATFORM_DCACHE_ALIGN, false); + if (!buf) { + LOG_ERR("Failed to allocate buffer %u (size %zu)", bdesc->id, bdesc->size); + return -ENOMEM; + } + + /* Resolve buffer parameters: rate, channels, format, and stream direction */ + uint32_t rate = bdesc->rate; + uint16_t channels = bdesc->channels; + enum sof_ipc_frame fmt = bdesc->fmt; + uint32_t dir = SOF_IPC_STREAM_PLAYBACK; + + /* Look up the producer component connected to this buffer via route table */ + const struct sof_static_comp *prod_comp = NULL; + for (size_t r = 0; r < topo->num_routes; r++) { + if (topo->routes[r].buffer_id == bdesc->id) { + uint32_t src_id = topo->routes[r].src_comp_id; + for (size_t c = 0; c < topo->num_comps; c++) { + if (topo->comps[c].id == src_id) { + prod_comp = &topo->comps[c]; + break; + } + } + break; + } + } + + /* Inherit parameters from producer component */ + if (prod_comp) { + dir = prod_comp->direction; + if (fmt == SOF_IPC_FRAME_INVALID) + fmt = prod_comp->caps.default_fmt; + if (!rate) { + /* If producer is a rate converter (SRC / ASRC), inherit sink_rate */ + if (prod_comp->caps.sink_rate) + rate = prod_comp->caps.sink_rate; + else + rate = prod_comp->caps.default_rate; + } + if (!channels) + channels = prod_comp->caps.max_channels; + } + + /* If rate or channels still unassigned, inherit from producer's owning pipeline */ + if (!rate && prod_comp) { + const struct sof_static_pipeline_desc *pdesc = + sof_static_find_pipeline_desc(topo, prod_comp->pipeline_id); + if (pdesc && pdesc->default_rate) + rate = pdesc->default_rate; + } + + /* Default fallbacks if unassigned */ + if (!rate) + rate = 48000; + if (!channels) + channels = 2; + if (fmt == SOF_IPC_FRAME_INVALID) + fmt = SOF_IPC_FRAME_S16_LE; + + /* Initialize circular buffer stream parameters and reset pointers */ + init_buffer_params(buf, dir, fmt, rate, channels); + + /* Record buffer instance in static tracking array */ + s_buffers[s_num_buffers] = buf; + s_buffer_ids[s_num_buffers] = bdesc->id; + s_num_buffers++; + } + + return 0; +} + +/** + * \brief Stage 4: Connect pipeline graph routes. + * + * Establish graph connections: producer comp -> intermediate buffer -> consumer comp. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_init_routes(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_routes; i++) { + const struct sof_static_route *r = &topo->routes[i]; + struct comp_dev *src = sof_static_comp_get(r->src_comp_id); + struct comp_buffer *buf = sof_static_buffer_get(r->buffer_id); + struct comp_dev *sink = sof_static_comp_get(r->sink_comp_id); + + /* Verify all route endpoints exist */ + if (!src || !buf || !sink) { + LOG_ERR("Failed route %u -> [buf %u] -> %u", r->src_comp_id, r->buffer_id, r->sink_comp_id); + return -EINVAL; + } + + /* Connect producer component to buffer and buffer to consumer component */ + int ret = pipeline_connect(src, buf, PPL_CONN_DIR_COMP_TO_BUFFER); + if (ret < 0) { + LOG_ERR("Failed pipeline_connect src %u -> buf %u: %d", + r->src_comp_id, r->buffer_id, ret); + return ret; + } + + ret = pipeline_connect(sink, buf, PPL_CONN_DIR_BUFFER_TO_COMP); + if (ret < 0) { + LOG_ERR("Failed pipeline_connect buf %u -> sink %u: %d", + r->buffer_id, r->sink_comp_id, ret); + return ret; + } + + LOG_DBG("Connected: %u -> [buf %u] -> %u", r->src_comp_id, r->buffer_id, r->sink_comp_id); + } + + return 0; +} + +/** + * \brief Stage 5: Set pipeline endpoints and propagate parameters. + * + * Bind source, sink, and scheduling components to each pipeline. + * Propagate pipeline_params() and pipeline_prepare() to initiate graph-wide + * parameter negotiation across constituent components and buffers. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_init_pipeline_endpoints(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_pipelines; i++) { + const struct sof_static_pipeline_desc *pdesc = &topo->pipelines[i]; + struct pipeline *pipe = sof_static_pipeline_get(pdesc->pipeline_id); + if (!pipe) { + LOG_ERR("Pipeline %u not found", pdesc->pipeline_id); + return -EINVAL; + } + + /* Bind primary source, sink, and scheduling components */ + pipe->source_comp = sof_static_comp_get(pdesc->source_comp_id); + pipe->sink_comp = sof_static_comp_get(pdesc->sink_comp_id); + pipe->sched_comp = sof_static_comp_get(pdesc->sched_comp_id); + + if (!pipe->source_comp || !pipe->sink_comp) { + LOG_ERR("Pipeline %u missing primary source (ID %u) or sink (ID %u)", + pdesc->pipeline_id, pdesc->source_comp_id, pdesc->sink_comp_id); + return -EINVAL; + } + + /* Resolve the component driving pipeline parameters */ + struct comp_dev *host_or_sched = pipe->sched_comp ? pipe->sched_comp : pipe->source_comp; + if (host_or_sched) { + const struct sof_static_comp *cdesc = NULL; + for (size_t c = 0; c < topo->num_comps; c++) { + if (topo->comps[c].id == dev_comp_id(host_or_sched)) { + cdesc = &topo->comps[c]; + break; + } + } + + /* Determine stream sampling rate for this pipeline */ + uint32_t rate = pdesc->default_rate; + if (!rate && cdesc) + rate = cdesc->caps.default_rate; + if (!rate) + rate = 48000; + + /* Determine stream channel count */ + uint32_t channels = pdesc->default_channels; + if (!channels && cdesc) + channels = cdesc->caps.max_channels; + if (!channels) + channels = 2; + + /* Determine stream PCM frame format */ + enum sof_ipc_frame fmt = (cdesc && cdesc->caps.default_fmt) ? + cdesc->caps.default_fmt : host_or_sched->ipc_config.frame_fmt; + if (!fmt) + fmt = SOF_IPC_FRAME_S16_LE; + + /* Calculate container bytes, valid bytes, and period frame size */ + uint32_t cont_bytes = get_sample_bytes(fmt); + uint32_t valid_bytes = static_get_sample_valid_bytes(fmt); + uint32_t period_us = pdesc->period ? pdesc->period : 1000; + uint32_t frames = pdesc->frames_per_sched ? pdesc->frames_per_sched : + ((rate * period_us) / 1000000); + if (frames == 0) + frames = 48; + + /* Populate PCM params structure for the pipeline */ + struct sof_ipc_pcm_params prms; + memset(&prms, 0, sizeof(prms)); + prms.params.rate = rate; + prms.params.channels = channels; + prms.params.frame_fmt = fmt; + prms.params.sample_container_bytes = cont_bytes; + prms.params.sample_valid_bytes = valid_bytes; + prms.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + prms.params.host_period_bytes = frames * channels * cont_bytes; + prms.comp_id = dev_comp_id(host_or_sched); + prms.params.direction = pdesc->direction; + for (uint32_t c = 0; c < SOF_IPC_MAX_CHANNELS; c++) { + if (c == 0) + prms.params.chmap[c] = (channels == 1) ? SOF_CHMAP_MONO : SOF_CHMAP_FL; + else if (c == 1 && channels > 1) + prms.params.chmap[c] = SOF_CHMAP_FR; + else if (c < channels) + prms.params.chmap[c] = c + 1; + else + prms.params.chmap[c] = SOF_CHMAP_NA; + } + + /* Propagate params and prepare throughout the pipeline walk */ + int ret_prms = pipeline_params(pipe, host_or_sched, &prms); + if (ret_prms < 0) { + LOG_ERR("Pipeline %u params failed: %d", pdesc->pipeline_id, ret_prms); + return ret_prms; + } + int ret_prep = pipeline_prepare(pipe, host_or_sched); + if (ret_prep < 0) { + LOG_ERR("Pipeline %u prepare failed: %d", pdesc->pipeline_id, ret_prep); + return ret_prep; + } + LOG_INF("Pipeline %u params (rate %u, ch %u, fmt %u) OK, prepare OK", + pdesc->pipeline_id, rate, channels, fmt); + } + } + + return 0; +} + +/** + * \brief Stage 6: Explicitly configure and prepare all components. + * + * Ensure every instantiated component transitions to COMP_STATE_PREPARE, + * even if not reached by the initial pipeline walk. This guarantees processing + * modules (e.g. Volume, EQ, DRC, Selector) are fully primed and ready for streaming. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_prepare_comps(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_comps; i++) { + struct comp_dev *dev = sof_static_comp_get(topo->comps[i].id); + if (!dev) + continue; + + /* Only prepare components currently in READY state */ + if (dev->state == COMP_STATE_READY) { + const struct sof_static_comp *cdesc = &topo->comps[i]; + const struct sof_static_pipeline_desc *pdesc = + sof_static_find_pipeline_desc(topo, cdesc->pipeline_id); + + /* Determine component-specific sample rate */ + uint32_t rate = cdesc->caps.default_rate; + if (!rate && pdesc) + rate = pdesc->default_rate; + if (!rate) + rate = 48000; + + /* Determine channel count */ + uint32_t channels = cdesc->caps.max_channels; + if (!channels && pdesc) + channels = pdesc->default_channels; + if (!channels) + channels = 2; + + /* Determine frame format */ + enum sof_ipc_frame fmt = cdesc->caps.default_fmt ? cdesc->caps.default_fmt : + dev->ipc_config.frame_fmt; + if (!fmt) + fmt = SOF_IPC_FRAME_S16_LE; + + /* Calculate container size, valid bits, and frames */ + uint32_t cont_bytes = get_sample_bytes(fmt); + uint32_t valid_bytes = static_get_sample_valid_bytes(fmt); + uint32_t period_us = (pdesc && pdesc->period) ? pdesc->period : 1000; + uint32_t frames = (pdesc && pdesc->frames_per_sched) ? pdesc->frames_per_sched : + ((rate * period_us) / 1000000); + if (frames == 0) + frames = 48; + + /* Populate component PCM parameters */ + struct sof_ipc_pcm_params prms; + memset(&prms, 0, sizeof(prms)); + prms.params.rate = rate; + prms.params.channels = channels; + prms.params.frame_fmt = fmt; + prms.params.sample_container_bytes = cont_bytes; + prms.params.sample_valid_bytes = valid_bytes; + prms.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + prms.params.host_period_bytes = frames * channels * cont_bytes; + prms.comp_id = dev_comp_id(dev); + prms.params.direction = dev->direction; + for (uint32_t c = 0; c < SOF_IPC_MAX_CHANNELS; c++) { + if (c == 0) + prms.params.chmap[c] = (channels == 1) ? SOF_CHMAP_MONO : SOF_CHMAP_FL; + else if (c == 1 && channels > 1) + prms.params.chmap[c] = SOF_CHMAP_FR; + else if (c < channels) + prms.params.chmap[c] = c + 1; + else + prms.params.chmap[c] = SOF_CHMAP_NA; + } + + /* Call component params and prepare callbacks */ + int ret_prms = comp_params(dev, &prms.params); + if (ret_prms < 0) { + LOG_ERR("Component %u ('%s') params failed: %d", + dev_comp_id(dev), cdesc->name, ret_prms); + return ret_prms; + } + int ret_prep = comp_prepare(dev); + if (ret_prep < 0) { + LOG_ERR("Component %u ('%s') prepare failed: %d", + dev_comp_id(dev), cdesc->name, ret_prep); + return ret_prep; + } + LOG_INF("Component %u ('%s') explicit prepare OK (state %d)", + dev_comp_id(dev), cdesc->name, dev->state); + } + } + + return 0; +} + +/** + * \brief Stage 7: Initialize kcontrols. + * + * Set up all declared volume, mute, enum, and binary kcontrols with their + * default boot values so processing modules start in their expected state. + * + * \param[in] topo Topology descriptor pointer. + * \return 0 on success, negative errno on failure. + */ +static int sof_static_topology_init_controls(const struct sof_static_topology *topo) +{ + for (size_t i = 0; i < topo->num_controls && i < MAX_STATIC_CONTROLS; i++) { + const struct sof_static_kcontrol *ctl = &topo->controls[i]; + s_control_vals[i] = ctl->def; + int ret = sof_static_kcontrol_set(ctl->id, ctl->def); + if (ret < 0) { + LOG_ERR("Failed to set default value for control [%u] '%s': %d", + ctl->id, ctl->name, ret); + return ret; + } + LOG_INF("Kcontrol [%u] '%s' (comp %u, type %d, def %d)", + ctl->id, ctl->name, ctl->target_comp_id, ctl->type, ctl->def); + } + + return 0; +} + +/** + * \brief Load and instantiate a declarative static audio topology. + * + * Executes the complete multi-stage construction pipeline: + * Stage 1: Allocate top-level pipelines + * Stage 2: Instantiate components (modules, DAIs, USB/host) + * Stage 3: Allocate intermediate audio buffers + * Stage 4: Connect pipeline graph routes + * Stage 5: Bind pipeline endpoints and propagate stream parameters + * Stage 6: Explicitly configure and prepare all components + * Stage 7: Initialize kcontrols with default boot values + * + * \param[in] topo Pointer to declarative static topology descriptor. + * \return 0 on success, negative errno on failure. + */ +/** + * \brief Free all allocated topology resources and reset tracking tables. + * + * Tears down allocated buffers, components, and pipelines in reverse dependency + * order, clears tracking registries, and resets active topology state. + */ +static void sof_static_topology_unwind(void) +{ + /* Free allocated intermediate buffers */ + for (size_t i = 0; i < s_num_buffers; i++) { + if (s_buffers[i]) { + buffer_free(s_buffers[i]); + s_buffers[i] = NULL; + } + } + s_num_buffers = 0; + + /* Free instantiated components */ + for (size_t i = 0; i < s_num_comps; i++) { + if (s_comps[i]) { + comp_free(s_comps[i]); + s_comps[i] = NULL; + } + } + s_num_comps = 0; + + /* Free allocated pipelines */ + for (size_t i = 0; i < s_num_pipelines; i++) { + if (s_pipelines[i]) { + pipeline_free(s_pipelines[i]); + s_pipelines[i] = NULL; + } + } + s_num_pipelines = 0; + + /* Reset kcontrol cache and unpublish active topology pointer */ + memset(s_control_vals, 0, sizeof(s_control_vals)); + s_active_topo = NULL; +} + +int sof_static_topology_init(const struct sof_static_topology *topo) +{ + int ret; + + /* Validate input topology pointer */ + if (!topo) { + LOG_ERR("Invalid topology descriptor"); + return -EINVAL; + } + + /* Validate topology descriptor counts against compiled capacity limits */ + if (topo->num_pipelines > MAX_STATIC_PIPELINES || + topo->num_comps > MAX_STATIC_COMPS || + topo->num_buffers > MAX_STATIC_BUFFERS || + topo->num_controls > MAX_STATIC_CONTROLS || + topo->num_routes > MAX_STATIC_ROUTES) { + LOG_ERR("Topology descriptor counts exceed compiled limits (pipes %zu/%d, comps %zu/%d, bufs %zu/%d, ctls %zu/%d, routes %zu/%d)", + topo->num_pipelines, MAX_STATIC_PIPELINES, + topo->num_comps, MAX_STATIC_COMPS, + topo->num_buffers, MAX_STATIC_BUFFERS, + topo->num_controls, MAX_STATIC_CONTROLS, + topo->num_routes, MAX_STATIC_ROUTES); + return -EINVAL; + } + + LOG_INF("=== Loading Static Audio Topology: '%s' ===", topo->name ? topo->name : "Unnamed"); + + /* Clean up any previously allocated state before constructing new topology */ + sof_static_topology_unwind(); + + /* Stage 1: Allocate top-level pipelines */ + ret = sof_static_topology_init_pipelines(topo); + if (ret < 0) { + LOG_ERR("sof_static_topology_init: stage 1 (init_pipelines) failed: %d", ret); + goto err_unwind; + } + + /* Stage 2: Instantiate components */ + ret = sof_static_topology_init_comps(topo); + if (ret < 0) { + LOG_ERR("sof_static_topology_init: stage 2 (init_comps) failed: %d", ret); + goto err_unwind; + } + + /* Stage 3: Allocate intermediate audio buffers */ + ret = sof_static_topology_init_buffers(topo); + if (ret < 0) { + LOG_ERR("sof_static_topology_init: stage 3 (init_buffers) failed: %d", ret); + goto err_unwind; + } + + /* Stage 4: Connect pipeline graph routes */ + ret = sof_static_topology_init_routes(topo); + if (ret < 0) { + LOG_ERR("sof_static_topology_init: stage 4 (init_routes) failed: %d", ret); + goto err_unwind; + } + + /* Stage 5: Bind pipeline endpoints and propagate stream parameters */ + ret = sof_static_topology_init_pipeline_endpoints(topo); + if (ret < 0) { + LOG_ERR("sof_static_topology_init: stage 5 (init_pipeline_endpoints) failed: %d", ret); + goto err_unwind; + } + + /* Stage 6: Explicitly configure and prepare all components */ + ret = sof_static_topology_prepare_comps(topo); + if (ret < 0) { + LOG_ERR("sof_static_topology_init: stage 6 (prepare_comps) failed: %d", ret); + goto err_unwind; + } + + /* Stage 7: Initialize kcontrols with default values */ + ret = sof_static_topology_init_controls(topo); + if (ret < 0) { + LOG_ERR("sof_static_topology_init: stage 7 (init_controls) failed: %d", ret); + goto err_unwind; + } + + /* Publish topology as active only after all construction stages succeed */ + s_active_topo = topo; + + LOG_INF("Static audio topology initialized successfully (%zu pipelines, %zu comps, %zu buffers, %zu controls)", + topo->num_pipelines, topo->num_comps, topo->num_buffers, topo->num_controls); + return 0; + +err_unwind: + sof_static_topology_unwind(); + return ret; +} + +/** + * \brief Retrieve the currently active static topology descriptor. + * \return Pointer to active struct sof_static_topology, or NULL if uninitialized. + */ +const struct sof_static_topology *sof_static_topology_get(void) +{ + return s_active_topo; +} + +/** + * \brief Retrieve a pipeline instance pointer by its unique pipeline ID. + * \param[in] pipeline_id Unique pipeline ID to search for. + * \return Pointer to struct pipeline, or NULL if not found. + */ +struct pipeline *sof_static_pipeline_get(uint32_t pipeline_id) +{ + /* Linear scan of statically tracked pipeline IDs */ + for (size_t i = 0; i < s_num_pipelines; i++) { + if (s_pipeline_ids[i] == pipeline_id) + return s_pipelines[i]; + } + + LOG_ERR("sof_static_pipeline_get: pipeline %u not found", pipeline_id); + return NULL; +} + +/** + * \brief Retrieve a component device pointer by its unique component ID. + * \param[in] comp_id Unique component ID to search for. + * \return Pointer to struct comp_dev, or NULL if not found. + */ +struct comp_dev *sof_static_comp_get(uint32_t comp_id) +{ + /* Linear scan of statically tracked component IDs */ + for (size_t i = 0; i < s_num_comps; i++) { + if (s_comp_ids[i] == comp_id) + return s_comps[i]; + } + + LOG_ERR("sof_static_comp_get: comp %u not found", comp_id); + return NULL; +} + +/** + * \brief Retrieve an intermediate audio buffer pointer by its unique buffer ID. + * \param[in] buffer_id Unique buffer ID to search for. + * \return Pointer to struct comp_buffer, or NULL if not found. + */ +struct comp_buffer *sof_static_buffer_get(uint32_t buffer_id) +{ + /* Linear scan of statically tracked buffer IDs */ + for (size_t i = 0; i < s_num_buffers; i++) { + if (s_buffer_ids[i] == buffer_id) + return s_buffers[i]; + } + + LOG_ERR("sof_static_buffer_get: buffer %u not found", buffer_id); + return NULL; +} + +/** + * \brief Set kcontrol value by control ID. + * + * Dispatches control updates to the custom callback handler if registered, + * or routes volume, switch, or enum controls to target components via their + * registered static module operations (struct sof_static_module_ops). + * + * \param[in] ctrl_id Unique control identifier. + * \param[in] val Value to apply. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set(uint32_t ctrl_id, int32_t val) +{ + if (!s_active_topo) + return -ENODEV; + + /* Look up control descriptor by unique ID */ + const struct sof_static_kcontrol *ctl = NULL; + size_t ctl_idx = 0; + for (size_t i = 0; i < s_active_topo->num_controls; i++) { + if (s_active_topo->controls[i].id == ctrl_id) { + ctl = &s_active_topo->controls[i]; + ctl_idx = i; + break; + } + } + if (!ctl) { + LOG_ERR("sof_static_kcontrol_set: control ID %u not found", ctrl_id); + return -ENOENT; + } + + /* + * Custom kcontrol callback handler: + * Allows board- or platform-specific controls (such as hardware clock + * switching, route multiplexers, or DMIC injection) to intercept control events. + */ + if (s_active_topo->custom_control_handler) { + int ret = s_active_topo->custom_control_handler(ctl, val, s_active_topo->custom_control_data); + if (ret >= 0 || ctl->target_comp_id == 0) { + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' set to %d via custom handler", ctl->id, ctl->name, val); + return ret; + } + } + + /* Virtual / platform controls with no target component */ + if (ctl->target_comp_id == 0) { + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' stored val=%d", ctl->id, ctl->name, val); + return 0; + } + + /* Look up target component device */ + struct comp_dev *dev = sof_static_comp_get(ctl->target_comp_id); + if (!dev) + return -ENODEV; + + /* Binary controls cannot be updated with a scalar int32_t value */ + if (ctl->type == SOF_STATIC_CTRL_BINARY) { + LOG_ERR("sof_static_kcontrol_set: ctl [%u] '%s' is binary, use sof_static_kcontrol_set_binary", + ctl->id, ctl->name); + return -EINVAL; + } + + /* Look up component's registered static module operations */ + const struct sof_static_module_ops *ops = dev->drv ? sof_static_find_module_ops(dev->drv->uid) : NULL; + if (!ops) { + LOG_ERR("sof_static_kcontrol_set: no static ops for comp %u (ctl [%u] '%s')", + ctl->target_comp_id, ctl->id, ctl->name); + return -ENOTSUP; + } + + int ret_op = -ENOTSUP; + + /* Dispatch control value to target component using registered operations */ + switch (ctl->type) { + case SOF_STATIC_CTRL_VOLUME: + /* Linear volume fader control */ + if (ops->apply_volume) + ret_op = ops->apply_volume(dev, ctl->channels, val); + break; + + case SOF_STATIC_CTRL_SWITCH: + /* Boolean mute or module bypass switch */ + if (ops->apply_switch) + ret_op = ops->apply_switch(dev, ctl->channels, val); + break; + + case SOF_STATIC_CTRL_ENUM: + /* Enumerated route or channel selector */ + if (ops->apply_enum) + ret_op = ops->apply_enum(dev, 0, val); + break; + + default: + LOG_ERR("sof_static_kcontrol_set: invalid control type %d for ctl %u", + ctl->type, ctl->id); + return -EINVAL; + } + + if (ret_op < 0) { + LOG_ERR("sof_static_kcontrol_set: op failed %d for ctl [%u] '%s'", + ret_op, ctl->id, ctl->name); + return ret_op; + } + + /* Cache updated control value and log confirmation */ + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' set to %d", ctl->id, ctl->name, val); + return 0; +} + +/** + * \brief Set kcontrol value for a specific audio channel. + * \param[in] ctrl_id Unique control identifier. + * \param[in] channel Channel index (0-based). + * \param[in] val Value to apply. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set_chan(uint32_t ctrl_id, uint32_t channel, int32_t val) +{ + /* Ensure an active static topology has been initialized */ + if (!s_active_topo) { + LOG_ERR("sof_static_kcontrol_set_chan: no active topology"); + return -ENODEV; + } + + const struct sof_static_kcontrol *ctl = NULL; + size_t ctl_idx = 0; + + /* Find control descriptor by ID */ + for (size_t i = 0; i < s_active_topo->num_controls; i++) { + if (s_active_topo->controls[i].id == ctrl_id) { + ctl = &s_active_topo->controls[i]; + ctl_idx = i; + break; + } + } + + if (!ctl) { + LOG_ERR("sof_static_kcontrol_set_chan: control ID %u not found", ctrl_id); + return -ENOENT; + } + + /* Validate channel index against control channel count */ + if (channel >= ctl->channels) { + LOG_ERR("sof_static_kcontrol_set_chan: channel %u out of range (max %u) for ctl %u", + channel, ctl->channels, ctrl_id); + return -EINVAL; + } + + /* + * Custom kcontrol callback handler: + * Forward to platform-specific hook if registered, or acknowledge virtual controls. + */ + if (s_active_topo->custom_control_handler) { + int ret = s_active_topo->custom_control_handler(ctl, val, s_active_topo->custom_control_data); + if (ret >= 0 || ctl->target_comp_id == 0) { + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' ch %u set to %d via custom handler", + ctl->id, ctl->name, channel, val); + return ret; + } + } + + /* Virtual / platform controls with no target component */ + if (ctl->target_comp_id == 0) { + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' ch %u stored val=%d", ctl->id, ctl->name, channel, val); + return 0; + } + + /* Look up target component device */ + struct comp_dev *dev = sof_static_comp_get(ctl->target_comp_id); + if (!dev) { + LOG_ERR("sof_static_kcontrol_set_chan: comp %u not found for ctl %u", + ctl->target_comp_id, ctrl_id); + return -ENODEV; + } + + /* Binary controls cannot be updated with a channel scalar value */ + if (ctl->type == SOF_STATIC_CTRL_BINARY) { + LOG_ERR("sof_static_kcontrol_set_chan: ctl [%u] '%s' is binary, use sof_static_kcontrol_set_binary", + ctl->id, ctl->name); + return -EINVAL; + } + + /* Look up component's registered static module operations */ + const struct sof_static_module_ops *ops = dev->drv ? sof_static_find_module_ops(dev->drv->uid) : NULL; + if (!ops) { + LOG_ERR("sof_static_kcontrol_set_chan: no static ops for comp %u (ctl [%u] '%s')", + ctl->target_comp_id, ctl->id, ctl->name); + return -ENOTSUP; + } + + int ret_op = -ENOTSUP; + + /* Dispatch control value to target component using channel-specific ops if available */ + switch (ctl->type) { + case SOF_STATIC_CTRL_VOLUME: + /* Linear volume: prefer channel-aware callback when provided */ + if (ops->apply_volume_chan) + ret_op = ops->apply_volume_chan(dev, channel, val); + else if (ops->apply_volume) + ret_op = ops->apply_volume(dev, ctl->channels, val); + break; + + case SOF_STATIC_CTRL_SWITCH: + /* Boolean switch / mute: prefer channel-aware callback when provided */ + if (ops->apply_switch_chan) + ret_op = ops->apply_switch_chan(dev, channel, val); + else if (ops->apply_switch) + ret_op = ops->apply_switch(dev, ctl->channels, val); + break; + + case SOF_STATIC_CTRL_ENUM: + /* Channel / stream multiplexer selector */ + if (ops->apply_enum) + ret_op = ops->apply_enum(dev, channel, val); + break; + + default: + LOG_ERR("sof_static_kcontrol_set_chan: invalid control type %d for ctl %u", + ctl->type, ctl->id); + return -EINVAL; + } + + if (ret_op < 0) { + LOG_ERR("sof_static_kcontrol_set_chan: op failed %d for ctl [%u] '%s'", + ret_op, ctl->id, ctl->name); + return ret_op; + } + + /* Cache updated control value and log confirmation */ + s_control_vals[ctl_idx] = val; + LOG_INF("Kcontrol [%u] '%s' ch %u set to %d", ctl->id, ctl->name, channel, val); + return 0; +} + +/** + * \brief Set binary raw configuration blob on a static kcontrol by control ID. + * + * Dispatches the raw binary configuration blob to the target component associated + * with the designated kcontrol via apply_binary or module adapter large config. + * + * \param[in] ctrl_id Target kcontrol identifier. + * \param[in] data Pointer to raw configuration blob. + * \param[in] size Size of data blob in bytes. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set_binary(uint32_t ctrl_id, const void *data, size_t size) +{ + /* Ensure an active static topology has been initialized */ + if (!s_active_topo) { + LOG_ERR("sof_static_kcontrol_set_binary: no active topology"); + return -ENODEV; + } + if (!data || size == 0) { + LOG_ERR("sof_static_kcontrol_set_binary: invalid NULL or empty data"); + return -EINVAL; + } + + const struct sof_static_kcontrol *ctl = NULL; + for (size_t i = 0; i < s_active_topo->num_controls; i++) { + if (s_active_topo->controls[i].id == ctrl_id) { + ctl = &s_active_topo->controls[i]; + break; + } + } + if (!ctl) { + LOG_ERR("sof_static_kcontrol_set_binary: control ID %u not found", ctrl_id); + return -ENOENT; + } + if (ctl->type != SOF_STATIC_CTRL_BINARY) { + LOG_ERR("sof_static_kcontrol_set_binary: control [%u] '%s' is not binary type", + ctl->id, ctl->name); + return -EINVAL; + } + if (ctl->target_comp_id == 0) { + LOG_INF("Kcontrol [%u] '%s' binary blob (%zu bytes) processed", ctl->id, ctl->name, size); + return 0; + } + + struct comp_dev *dev = sof_static_comp_get(ctl->target_comp_id); + if (!dev) { + LOG_ERR("sof_static_kcontrol_set_binary: target comp %u not found", ctl->target_comp_id); + return -ENODEV; + } + + const struct sof_static_module_ops *ops = dev->drv ? sof_static_find_module_ops(dev->drv->uid) : NULL; + if (ops && ops->apply_binary) { + int ret_op = ops->apply_binary(dev, data, size); + if (ret_op < 0) { + LOG_ERR("sof_static_kcontrol_set_binary: apply_binary failed %d for ctl [%u] '%s'", + ret_op, ctl->id, ctl->name); + return ret_op; + } + LOG_INF("Kcontrol [%u] '%s' binary set (%zu bytes)", ctl->id, ctl->name, size); + return 0; + } + + /* Fallback: dispatch via component driver set_large_config or send_comp_config */ + int ret; + if (dev->drv && dev->drv->ops.set_large_config) + ret = dev->drv->ops.set_large_config(dev, 0, true, true, size, (const char *)data); + else + ret = send_comp_config(dev, data, size); + + if (ret < 0) { + LOG_ERR("sof_static_kcontrol_set_binary: binary set failed %d for ctl [%u] '%s'", + ret, ctl->id, ctl->name); + return ret; + } + + LOG_INF("Kcontrol [%u] '%s' binary set (%zu bytes)", ctl->id, ctl->name, size); + return 0; +} + +/** + * \brief Retrieve cached kcontrol value by control ID. + * \param[in] ctrl_id Unique control identifier. + * \param[out] val Pointer to store retrieved value. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_get(uint32_t ctrl_id, int32_t *val) +{ + if (!s_active_topo || !val) + return -EINVAL; + + /* Search control descriptors for matching ID and return cached value */ + for (size_t i = 0; i < s_active_topo->num_controls; i++) { + if (s_active_topo->controls[i].id == ctrl_id) { + *val = s_control_vals[i]; + return 0; + } + } + + LOG_ERR("sof_static_kcontrol_get: control ID %u not found", ctrl_id); + return -ENOENT; +} + +/** + * \brief Find kcontrol ID by its human-readable name. + * \param[in] name Control name string to search for. + * \return Control ID >= 0 if found, -ENOENT if not found. + */ +int sof_static_kcontrol_find_by_name(const char *name) +{ + if (!s_active_topo || !name) + return -EINVAL; + + /* Compare name string against declared controls in active topology */ + for (size_t i = 0; i < s_active_topo->num_controls; i++) { + if (s_active_topo->controls[i].name && + strcmp(s_active_topo->controls[i].name, name) == 0) { + return (int)s_active_topo->controls[i].id; + } + } + + LOG_ERR("sof_static_kcontrol_find_by_name: control '%s' not found", name); + return -ENOENT; +} + +/** + * \brief Resolve the primary trigger component driving a pipeline. + * + * For playback pipelines, the trigger component is typically sched_comp or source_comp. + * For capture pipelines, where data flows into the DSP from an external DAI or source, + * the trigger component must be the consumer/sink endpoint to pull data through the graph. + * + * \param[in] pipe Target pipeline pointer. + * \return Pointer to resolved trigger component device. + */ +static struct comp_dev *sof_static_pipeline_get_trigger_dev(struct pipeline *pipe) +{ + struct comp_dev *dev = pipe->sched_comp; + + /* For capture pipelines, ensure trigger starts from the sink component */ + if (!dev || (pipe->source_comp && pipe->source_comp->direction == SOF_IPC_STREAM_CAPTURE && + dev == pipe->source_comp)) { + if (pipe->source_comp && pipe->source_comp->direction == SOF_IPC_STREAM_CAPTURE) + dev = pipe->sink_comp; + else + dev = pipe->source_comp; + } + return dev; +} + +/** + * \brief Start a static audio pipeline by its pipeline ID. + * + * Prepares the pipeline and constituent components if needed, propagates + * start triggers, transitions state to COMP_STATE_ACTIVE, and activates + * scheduling copy tasks. + * + * \param[in] pipeline_id Target pipeline ID. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_start(uint32_t pipeline_id) +{ + /* Look up target pipeline */ + struct pipeline *pipe = sof_static_pipeline_get(pipeline_id); + if (!pipe) + return -ENOENT; + + /* Resolve trigger component */ + struct comp_dev *dev = sof_static_pipeline_get_trigger_dev(pipe); + if (!dev) + return -ENODEV; + + /* If not already active, prepare and trigger components */ + if (pipe->status != COMP_STATE_ACTIVE) { + /* + * Ensure pipeline is prepared before starting. + * If prepare fails, abort immediately: triggering a pipeline whose + * circular buffers and DMA channels are uninitialized causes DSP faults. + */ + if (pipe->status == COMP_STATE_READY || + pipe->status == COMP_STATE_INIT || + pipe->status == COMP_STATE_PAUSED) { + int ret_prep = pipeline_prepare(pipe, dev); + if (ret_prep < 0) { + LOG_ERR("Pipeline %u prepare failed: %d", pipeline_id, ret_prep); + return ret_prep; + } + } + + /* Propagate PRE_START trigger across the pipeline graph (pipeline_trigger_run + * automatically executes the follow-up START traversal on all graph components). + */ + int ret = pipeline_trigger_run(pipe, dev, COMP_TRIGGER_PRE_START); + if (ret < 0) { + LOG_ERR("Pipeline %u pre-start trigger failed: %d", pipeline_id, ret); + return ret; + } + + pipe->status = COMP_STATE_ACTIVE; + } + + /* Schedule periodic copy task if not already running */ + if (pipe->pipe_task && !task_is_active(pipe->pipe_task)) + pipeline_schedule_copy(pipe, 0); + + LOG_INF("Pipeline %u STARTED", pipeline_id); + return 0; +} + +/** + * \brief Stop a static audio pipeline by its pipeline ID. + * + * Propagates stop triggers, cancels scheduling copy tasks, and transitions + * pipeline and component states to COMP_STATE_PAUSED. + * + * \param[in] pipeline_id Target pipeline ID. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_stop(uint32_t pipeline_id) +{ + /* Look up target pipeline */ + struct pipeline *pipe = sof_static_pipeline_get(pipeline_id); + if (!pipe) + return -ENOENT; + + /* Resolve trigger component */ + struct comp_dev *dev = sof_static_pipeline_get_trigger_dev(pipe); + if (!dev) + return -ENODEV; + + /* Propagate STOP trigger if pipeline is active or paused */ + if (pipe->status == COMP_STATE_ACTIVE || pipe->status == COMP_STATE_PAUSED) { + int ret = pipeline_trigger_run(pipe, dev, COMP_TRIGGER_STOP); + if (ret < 0) { + LOG_ERR("Pipeline %u stop trigger failed: %d", pipeline_id, ret); + return ret; + } + + pipe->status = COMP_STATE_PAUSED; + } + + /* Cancel active periodic copy task */ + if (pipe->pipe_task && task_is_active(pipe->pipe_task)) + schedule_task_cancel(pipe->pipe_task); + + LOG_INF("Pipeline %u STOPPED", pipeline_id); + return 0; +} + +/** + * \brief Directly trigger a static pipeline start or stop by pipeline ID. + * + * Dispatches cleanly to sof_static_pipeline_start() or sof_static_pipeline_stop(). + * + * \param[in] pipeline_id Target pipeline ID. + * \param[in] start True to start pipeline, false to stop. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_trigger(uint32_t pipeline_id, bool start) +{ + if (start) + return sof_static_pipeline_start(pipeline_id); + + return sof_static_pipeline_stop(pipeline_id); +} + +/** + * \brief Re-negotiate the stream sample rate for a specific static pipeline. + * + * Re-negotiates the sample rate for the designated pipeline and its constituent + * components and buffers while the pipeline is in paused or stopped state. + * Validates that all constituent components support the requested rate according + * to their capability masks, reconfigures buffer period sizes, and propagates + * pipeline_params() and pipeline_prepare() through the pipeline graph. + * + * \param[in] pipeline_id Target pipeline identifier. + * \param[in] rate Target sample rate in Hz (e.g. 16000, 44100, 48000, 96000). + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_set_sample_rate(uint32_t pipeline_id, uint32_t rate) +{ + /* Verify target pipeline exists */ + struct pipeline *pipe = sof_static_pipeline_get(pipeline_id); + if (!pipe) { + LOG_ERR("sof_static_pipeline_set_sample_rate: pipeline %u not found", pipeline_id); + return -ENOENT; + } + + /* Dynamic rate reconfiguration requires the pipeline to be stopped/paused */ + if (pipe->status == COMP_STATE_ACTIVE) { + LOG_ERR("sof_static_pipeline_set_sample_rate: pipeline %u is active, cannot change rate", + pipeline_id); + return -EBUSY; + } + + /* Validate requested sample rate against component capability masks */ + uint32_t rmask = sof_static_rate_to_mask(rate); + if (s_active_topo) { + for (size_t c = 0; c < s_active_topo->num_comps; c++) { + const struct sof_static_comp *cdesc = &s_active_topo->comps[c]; + if (cdesc->pipeline_id == pipeline_id && cdesc->caps.rates) { + if (!(cdesc->caps.rates & rmask)) { + LOG_ERR("sof_static_pipeline_set_sample_rate: rate %u Hz unsupported by comp %u ('%s')", + rate, cdesc->id, cdesc->name); + return -EINVAL; + } + } + } + } + + /* Record updated sample rate */ + for (size_t i = 0; i < s_num_pipelines; i++) { + if (s_pipeline_ids[i] == pipeline_id) { + s_pipeline_rates[i] = rate; + break; + } + } + + /* Update buffer parameters for all buffers whose producer belongs to this pipeline */ + if (s_active_topo) { + for (size_t r = 0; r < s_active_topo->num_routes; r++) { + const struct sof_static_route *route = &s_active_topo->routes[r]; + const struct sof_static_comp *src_cdesc = + sof_static_find_comp_desc(s_active_topo, route->src_comp_id); + + if (src_cdesc && src_cdesc->pipeline_id == pipeline_id) { + struct comp_buffer *buf = sof_static_buffer_get(route->buffer_id); + + if (buf) { + init_buffer_params(buf, src_cdesc->direction, + audio_stream_get_frm_fmt(&buf->stream), + rate, + audio_stream_get_channels(&buf->stream)); + } + } + } + } + + /* Propagate updated stream parameters through pipeline graph */ + struct comp_dev *host_or_sched = pipe->sched_comp ? pipe->sched_comp : pipe->source_comp; + if (host_or_sched) { + const struct sof_static_pipeline_desc *pdesc = NULL; + if (s_active_topo) + pdesc = sof_static_find_pipeline_desc(s_active_topo, pipeline_id); + + uint32_t channels = 2; + enum sof_ipc_frame fmt = host_or_sched->ipc_config.frame_fmt; + if (!fmt) + fmt = SOF_IPC_FRAME_S16_LE; + + uint32_t cont_bytes = get_sample_bytes(fmt); + uint32_t valid_bytes = static_get_sample_valid_bytes(fmt); + uint32_t period_us = (pdesc && pdesc->period) ? pdesc->period : 1000; + uint32_t frames = (rate * period_us) / 1000000; + if (frames == 0) + frames = 48; + + struct sof_ipc_pcm_params prms; + memset(&prms, 0, sizeof(prms)); + prms.params.rate = rate; + prms.params.channels = channels; + prms.params.frame_fmt = fmt; + prms.params.sample_container_bytes = cont_bytes; + prms.params.sample_valid_bytes = valid_bytes; + prms.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + prms.params.host_period_bytes = frames * channels * cont_bytes; + prms.comp_id = dev_comp_id(host_or_sched); + prms.params.direction = host_or_sched->direction; + for (uint32_t c = 0; c < SOF_IPC_MAX_CHANNELS; c++) { + if (c == 0) + prms.params.chmap[c] = (channels == 1) ? SOF_CHMAP_MONO : SOF_CHMAP_FL; + else if (c == 1 && channels > 1) + prms.params.chmap[c] = SOF_CHMAP_FR; + else + prms.params.chmap[c] = SOF_CHMAP_NA; + } + + int ret_params = pipeline_params(pipe, host_or_sched, &prms); + if (ret_params < 0) { + LOG_ERR("sof_static_pipeline_set_sample_rate: pipeline_params failed %d for pipeline %u", + ret_params, pipeline_id); + return ret_params; + } + + int ret_prep = pipeline_prepare(pipe, host_or_sched); + if (ret_prep < 0) { + LOG_ERR("sof_static_pipeline_set_sample_rate: pipeline_prepare failed %d for pipeline %u", + ret_prep, pipeline_id); + return ret_prep; + } + } + + LOG_INF("Static pipeline %u sample rate reconfigured to %u Hz", pipeline_id, rate); + return 0; +} + +/** + * \brief Retrieve the current stream sample rate for a specific static pipeline. + * \param[in] pipeline_id Target pipeline identifier. + * \return Pipeline sample rate in Hz, or 0 if pipeline not found. + */ +uint32_t sof_static_pipeline_get_sample_rate(uint32_t pipeline_id) +{ + for (size_t i = 0; i < s_num_pipelines; i++) { + if (s_pipeline_ids[i] == pipeline_id) + return s_pipeline_rates[i] ? s_pipeline_rates[i] : 48000; + } + + LOG_ERR("sof_static_pipeline_get_sample_rate: pipeline %u not found", pipeline_id); + return 0; +} + +/** + * \brief Override the stream sample rate across all active static pipelines. + * \param[in] rate Target sample rate in Hz (e.g. 48000). + * \return 0 on success, negative errno on failure. + */ +int sof_static_set_sample_rate(uint32_t rate) +{ + if (s_num_pipelines == 0) { + LOG_ERR("sof_static_set_sample_rate: no active static pipelines"); + return -ENODEV; + } + + int last_ret = 0; + for (size_t i = 0; i < s_num_pipelines; i++) { + int ret = sof_static_pipeline_set_sample_rate(s_pipeline_ids[i], rate); + if (ret < 0) + last_ret = ret; + } + + LOG_INF("All static pipelines sample rate set to %u Hz", rate); + return last_ret; +} + +/** + * \brief Retrieve the primary stream sample rate from the first static pipeline. + * \return Current sample rate in Hz. + */ +uint32_t sof_static_get_sample_rate(void) +{ + if (s_num_pipelines > 0 && s_pipeline_rates[0]) + return s_pipeline_rates[0]; + + return 48000; +} diff --git a/src/audio/pipeline/static_pipeline_modules.c b/src/audio/pipeline/static_pipeline_modules.c new file mode 100644 index 000000000000..e280614782b4 --- /dev/null +++ b/src/audio/pipeline/static_pipeline_modules.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +/** + * \file audio/pipeline/static_pipeline_modules.c + * \brief Generic Static Module Operations Registry & Infrastructure + * \author Liam Girdwood + * + * Implements generic module operations registration, lookup, and default + * component initialization helpers for declarative static pipelines. + * This file contains purely generic infrastructure with zero per-module logic. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "static_pipeline_modules.h" + +LOG_MODULE_REGISTER(static_pipeline_modules, CONFIG_SOF_LOG_LEVEL); + +/* ========================================================================= + * Static Module Operations Registry + * ========================================================================= */ + +static APP_SYSUSER_DATA struct list_item s_module_ops_list = LIST_INIT(s_module_ops_list); + +/** + * \brief Register static module operations for an audio component. + * \param[in,out] ops Pointer to module operations structure. + * \return 0 on success, negative error code on failure. + */ +int sof_static_register_module_ops(struct sof_static_module_ops *ops) +{ + if (!ops) { + LOG_ERR("sof_static_register_module_ops: NULL ops pointer"); + return -EINVAL; + } + if (!ops->uuid) { + LOG_ERR("sof_static_register_module_ops: NULL ops UUID"); + return -EINVAL; + } + + list_item_append(&ops->list, &s_module_ops_list); + return 0; +} + +/** + * \brief Find registered static module operations by component UUID. + * \param[in] uuid UUID of target component. + * \return Pointer to registered ops, or NULL if not found or on error. + */ +const struct sof_static_module_ops *sof_static_find_module_ops(const struct sof_uuid *uuid) +{ + if (!uuid) { + LOG_ERR("sof_static_find_module_ops: NULL UUID pointer"); + return NULL; + } + + struct list_item *item; + list_for_item(item, &s_module_ops_list) { + struct sof_static_module_ops *ops = + container_of(item, struct sof_static_module_ops, list); + if (!memcmp(ops->uuid, uuid, UUID_SIZE)) + return ops; + } + + LOG_ERR("sof_static_find_module_ops: no ops registered for requested UUID"); + return NULL; +} + +/* ========================================================================= + * Base IPC4 Configuration & Default Component Creation + * ========================================================================= */ + +/* Channel map slot macro: channel index 'ch' placed at slot index 'slot' */ +#define SOF_CH_MAP_SLOT(slot, ch) (((uint32_t)((ch) & 0xf)) << ((slot) * 4)) + +#define SOF_CH_MAP_1CH(c0) \ + (0xfffffff0 | SOF_CH_MAP_SLOT(0, (c0))) + +#define SOF_CH_MAP_2CH(c0, c1) \ + (0xffffff00 | SOF_CH_MAP_SLOT(0, (c0)) | SOF_CH_MAP_SLOT(1, (c1))) + +#define SOF_CH_MAP_3CH(c0, c1, c2) \ + (0xfffff000 | SOF_CH_MAP_SLOT(0, (c0)) | SOF_CH_MAP_SLOT(1, (c1)) | \ + SOF_CH_MAP_SLOT(2, (c2))) + +#define SOF_CH_MAP_4CH(c0, c1, c2, c3) \ + (0xffff0000 | SOF_CH_MAP_SLOT(0, (c0)) | SOF_CH_MAP_SLOT(1, (c1)) | \ + SOF_CH_MAP_SLOT(2, (c2)) | SOF_CH_MAP_SLOT(3, (c3))) + +#define SOF_CH_MAP_6CH(c0, c1, c2, c3, c4, c5) \ + (0xff000000 | SOF_CH_MAP_SLOT(0, (c0)) | SOF_CH_MAP_SLOT(1, (c1)) | \ + SOF_CH_MAP_SLOT(2, (c2)) | SOF_CH_MAP_SLOT(3, (c3)) | \ + SOF_CH_MAP_SLOT(4, (c4)) | SOF_CH_MAP_SLOT(5, (c5))) + +#define SOF_CH_MAP_8CH(c0, c1, c2, c3, c4, c5, c6, c7) \ + (SOF_CH_MAP_SLOT(0, (c0)) | SOF_CH_MAP_SLOT(1, (c1)) | \ + SOF_CH_MAP_SLOT(2, (c2)) | SOF_CH_MAP_SLOT(3, (c3)) | \ + SOF_CH_MAP_SLOT(4, (c4)) | SOF_CH_MAP_SLOT(5, (c5)) | \ + SOF_CH_MAP_SLOT(6, (c6)) | SOF_CH_MAP_SLOT(7, (c7))) + +void sof_static_init_base_cfg(struct ipc4_base_module_cfg *base_cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ + if (!base_cfg || !cdesc) { + LOG_ERR("sof_static_init_base_cfg: NULL parameter"); + return; + } + + memset(base_cfg, 0, sizeof(*base_cfg)); + + uint32_t rate = cdesc->caps.default_rate ? cdesc->caps.default_rate : 48000; + uint16_t channels = cdesc->caps.max_channels ? cdesc->caps.max_channels : 2; + + /* + * Validate and clamp channel count before computing buffer sizes (IBS/OBS). + * Unsupported channel counts (> 8 or 0) default to standard stereo (2 channels) + * to ensure internally consistent IBS/OBS metrics and channel mapping metadata. + */ + if (channels == 0 || channels > 8) { + LOG_ERR("sof_static_init_base_cfg: unsupported channel count %u, clamping to stereo", + channels); + channels = 2; + } + + enum sof_ipc_frame fmt = cdesc->caps.default_fmt ? cdesc->caps.default_fmt : SOF_IPC_FRAME_S16_LE; + + uint32_t cont_bytes = get_sample_bytes(fmt); + uint32_t valid_bits = get_sample_bitdepth(fmt); + /* + * Special-case SOF_IPC_FRAME_S16_4LE: + * get_sample_bitdepth() returns 32 because it occupies a 32-bit container, + * but the valid PCM bit depth is 16 bits. + */ + if (fmt == SOF_IPC_FRAME_S16_4LE) + valid_bits = 16; + + uint32_t frames = (rate * (period_us ? period_us : 1000)) / 1000000; + + /* + * Fallback assumption: if integer division resulted in zero frames + * (e.g. invalid rate or ultra-low scheduling period), default to 48 frames, + * representing a standard 1 ms period at 48 kHz (48000 * 1000 / 1000000 = 48). + * Non-zero buffer size is required by IPC4 module infrastructure. + */ + if (frames == 0) + frames = 48; + + base_cfg->cpc = 0; + base_cfg->is_pages = 1; + base_cfg->ibs = frames * channels * cont_bytes; + + /* If sink_rate is specified (e.g. for SRC / ASRC), compute OBS from sink rate */ + if (cdesc->caps.sink_rate) { + uint32_t out_frames = (cdesc->caps.sink_rate * (period_us ? period_us : 1000)) / 1000000; + /* Fallback assumption: same 48-frame minimum for sink buffer */ + if (out_frames == 0) + out_frames = 48; + base_cfg->obs = out_frames * channels * cont_bytes; + } else { + base_cfg->obs = base_cfg->ibs; + } + + base_cfg->audio_fmt.sampling_frequency = rate; + base_cfg->audio_fmt.depth = (cont_bytes * 8); + base_cfg->audio_fmt.valid_bit_depth = valid_bits; + base_cfg->audio_fmt.channels_count = channels; + base_cfg->audio_fmt.interleaving_style = IPC4_CHANNELS_INTERLEAVED; + + /* Map PCM format to IPC4 sample type including unsigned integer for U8 */ + if (fmt == SOF_IPC_FRAME_FLOAT) + base_cfg->audio_fmt.s_type = IPC4_TYPE_FLOAT; + else if (fmt == SOF_IPC_FRAME_A_LAW) + base_cfg->audio_fmt.s_type = IPC4_TYPE_A_LAW; + else if (fmt == SOF_IPC_FRAME_MU_LAW) + base_cfg->audio_fmt.s_type = IPC4_TYPE_MU_LAW; + else if (fmt == SOF_IPC_FRAME_U8) + base_cfg->audio_fmt.s_type = IPC4_TYPE_UNSIGNED_INTEGER; + else + base_cfg->audio_fmt.s_type = IPC4_TYPE_SIGNED_INTEGER; + + /* Configure channel layout and channel slot map matching channel count */ + switch (channels) { + case 1: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_MONO; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_1CH(CHANNEL_CENTER); + break; + case 2: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_STEREO; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_2CH(CHANNEL_LEFT, CHANNEL_RIGHT); + break; + case 3: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_3_POINT_0; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_3CH(CHANNEL_LEFT, CHANNEL_CENTER, + CHANNEL_RIGHT); + break; + case 4: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_QUATRO; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_4CH(CHANNEL_LEFT, CHANNEL_RIGHT, + CHANNEL_LEFT_SURROUND, + CHANNEL_RIGHT_SURROUND); + break; + case 5: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_5_POINT_0; + base_cfg->audio_fmt.ch_map = (0xfff00000 | SOF_CH_MAP_SLOT(0, CHANNEL_LEFT) | + SOF_CH_MAP_SLOT(1, CHANNEL_CENTER) | + SOF_CH_MAP_SLOT(2, CHANNEL_RIGHT) | + SOF_CH_MAP_SLOT(3, CHANNEL_LEFT_SURROUND) | + SOF_CH_MAP_SLOT(4, CHANNEL_RIGHT_SURROUND)); + break; + case 6: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_5_POINT_1; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_6CH(CHANNEL_LEFT, CHANNEL_CENTER, + CHANNEL_RIGHT, CHANNEL_LEFT_SURROUND, + CHANNEL_RIGHT_SURROUND, CHANNEL_LFE); + break; + case 7: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_INVALID; + base_cfg->audio_fmt.ch_map = (0xf0000000 | SOF_CH_MAP_SLOT(0, CHANNEL_LEFT) | + SOF_CH_MAP_SLOT(1, CHANNEL_CENTER) | + SOF_CH_MAP_SLOT(2, CHANNEL_RIGHT) | + SOF_CH_MAP_SLOT(3, CHANNEL_LEFT_SURROUND) | + SOF_CH_MAP_SLOT(4, CHANNEL_RIGHT_SURROUND) | + SOF_CH_MAP_SLOT(5, CHANNEL_LEFT_SIDE) | + SOF_CH_MAP_SLOT(6, CHANNEL_RIGHT_SIDE)); + break; + case 8: + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_7_POINT_1; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_8CH(CHANNEL_LEFT, CHANNEL_CENTER, + CHANNEL_RIGHT, CHANNEL_LEFT_SURROUND, + CHANNEL_RIGHT_SURROUND, CHANNEL_LFE, + CHANNEL_LEFT_SIDE, CHANNEL_RIGHT_SIDE); + break; + default: + /* Defensive fallback for invalid channel count (clamped upfront) */ + base_cfg->audio_fmt.channels_count = 2; + base_cfg->audio_fmt.ch_cfg = IPC4_CHANNEL_CONFIG_STEREO; + base_cfg->audio_fmt.ch_map = SOF_CH_MAP_2CH(CHANNEL_LEFT, CHANNEL_RIGHT); + break; + } +} + +struct comp_dev *sof_static_module_create_default(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ + if (!drv || !cfg || !cdesc) { + LOG_ERR("sof_static_module_create_default: NULL parameter"); + return NULL; + } + + struct ipc4_base_module_cfg base_cfg; + + sof_static_init_base_cfg(&base_cfg, cdesc, period_us); + struct ipc_config_process base_proc_spec = { + .size = sizeof(base_cfg), + .data = (const uint8_t *)&base_cfg, + }; + + return drv->ops.create(drv, cfg, &base_proc_spec); +} diff --git a/src/audio/pipeline/static_pipeline_modules.h b/src/audio/pipeline/static_pipeline_modules.h new file mode 100644 index 000000000000..4ce8195722a5 --- /dev/null +++ b/src/audio/pipeline/static_pipeline_modules.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +/** + * \file audio/pipeline/static_pipeline_modules.h + * \brief Generic internal interfaces for static pipeline module operations. + * \author Liam Girdwood + * + * This private header declares internal helpers shared between the static + * pipeline loader and module adapter implementations. It contains no + * component-specific logic. + */ + +#ifndef __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_MODULES_H__ +#define __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_MODULES_H__ + +#include +#include + +/** + * \brief Synthesize standard IPC4 base module configuration structure. + * \param[out] base_cfg Target base configuration to populate. + * \param[in] cdesc Static component descriptor with format and rate capabilities. + * \param[in] period_us Pipeline scheduling period in microseconds. + */ +void sof_static_init_base_cfg(struct ipc4_base_module_cfg *base_cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us); + +/** + * \brief Default fallback creation for standard IPC4 module adapters. + * \param[in] drv SOF component driver. + * \param[in] cfg IPC component configuration. + * \param[in] cdesc Static component descriptor. + * \param[in] period_us Owning pipeline scheduling period in microseconds. + * \return Created comp_dev pointer, or NULL on failure. + */ +struct comp_dev *sof_static_module_create_default(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us); + +#endif /* __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_MODULES_H__ */ diff --git a/src/audio/pipeline/static_pipeline_uac2.c b/src/audio/pipeline/static_pipeline_uac2.c new file mode 100644 index 000000000000..7b4506d9289c --- /dev/null +++ b/src/audio/pipeline/static_pipeline_uac2.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Sound Open Firmware (SOF) Project +// +// Author: Liam Girdwood + +/** + * \file audio/pipeline/static_pipeline_uac2.c + * \brief USB Audio Class 2.0 (UAC2) Bridge & Control Operations for Static Pipelines + * \author Liam Girdwood + * + * Provides translation between USB Audio Class 2.0 (UAC2) host requests (volume, + * mute switch, streaming terminal triggers) and Sound Open Firmware static + * topology components and kcontrols. + */ + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(static_pipeline_uac2, CONFIG_SOF_LOG_LEVEL); + +/* Standard 0 dB gain constant for SOF volume representation (Q1.31 format) */ +#define SOF_VOL_ZERO_DB INT32_MAX + +#ifndef CONFIG_STATIC_PIPELINE_MAX_CONTROLS +#define CONFIG_STATIC_PIPELINE_MAX_CONTROLS 64 +#endif + +/* + * Cache structure storing raw UAC2 control values (signed 8.8 dB for volume, + * 0/1 for mute) per UAC2 entity and channel. + * Channel index 0 corresponds to Master; indices 1..SOF_IPC_MAX_CHANNELS correspond + * to individual audio channels. + */ +struct uac2_control_cache { + uint8_t entity_id; + bool is_volume; + bool valid; + int32_t val[SOF_IPC_MAX_CHANNELS + 1]; +}; + +static APP_SYSUSER_BSS struct uac2_control_cache s_uac2_cache[CONFIG_STATIC_PIPELINE_MAX_CONTROLS]; + +/** + * \brief Look up or allocate a cached control state entry for a UAC2 entity. + * \param[in] entity_id UAC2 Feature Unit Entity ID. + * \param[in] is_volume True for volume control, false for mute switch. + * \return Pointer to cache entry or NULL if table is full. + */ +static struct uac2_control_cache *get_uac2_cache(uint8_t entity_id, bool is_volume) +{ + /* Look for existing cache entry matching entity and control type */ + for (size_t i = 0; i < CONFIG_STATIC_PIPELINE_MAX_CONTROLS; i++) { + if (s_uac2_cache[i].valid && + s_uac2_cache[i].entity_id == entity_id && + s_uac2_cache[i].is_volume == is_volume) + return &s_uac2_cache[i]; + } + + /* Allocate a free cache slot if not already present */ + for (size_t i = 0; i < CONFIG_STATIC_PIPELINE_MAX_CONTROLS; i++) { + if (!s_uac2_cache[i].valid) { + s_uac2_cache[i].entity_id = entity_id; + s_uac2_cache[i].is_volume = is_volume; + s_uac2_cache[i].valid = true; + /* Default: volume 0 dB = 0x0000, mute = 0 (unmuted) */ + memset(s_uac2_cache[i].val, 0, sizeof(s_uac2_cache[i].val)); + return &s_uac2_cache[i]; + } + } + + return NULL; +} + +/** + * \brief Convert USB Audio Class 2.0 8.8 fixed-point dB volume to linear SOF volume. + * + * UAC2 expresses volume in 1/256 dB steps (e.g. 0x0000 = 0 dB, -90 dB = 0xa600). + * This helper translates UAC2 dB volume into SOF 32-bit linear volume using + * an integer 6 dB per bit shift approximation: + * - >= 0 dB maps to SOF_VOL_ZERO_DB (INT32_MAX, unity gain) + * - <= -90 dB maps to 0 (digital silence) + * - Values between -90 dB and 0 dB are scaled proportionally. + * + * \param[in] volume USB Audio Class 2.0 8.8 fixed-point dB volume value. + * \return 32-bit linear volume value (0 to SOF_VOL_ZERO_DB). + */ +static int32_t uac2_to_sof_volume(int16_t volume) +{ + /* Treat volume below -90 dB as complete digital silence */ + if (volume <= -90 * 256) + return 0; + + /* Clip volume at or above 0 dB to unity gain */ + if (volume >= 0) + return SOF_VOL_ZERO_DB; + + /* Convert negative dB (scaled by 256) to tenths of dB: 1 dB = ~6 dB per bit */ + int32_t db_x10 = (int32_t)(-volume) * 10 / 256; + int shift = db_x10 / 60; + if (shift >= 31) + return 0; + + /* + * Monotonically interpolate between 6 dB bit shifts. + * v is the gain at the current 6 dB boundary, and v_next is the gain at the + * next 6 dB boundary (v >> 1). We interpolate smoothly between v and v_next. + */ + int rem = db_x10 % 60; + uint64_t v = (uint64_t)SOF_VOL_ZERO_DB >> shift; + uint64_t v_next = v >> 1; + uint64_t val = v_next + (((v - v_next) * (60 - rem)) / 60); + + return (int32_t)val; +} + +/** + * \brief Dispatch USB Audio Class 2.0 Feature Unit control requests to matching kcontrols. + * + * Maps incoming USB Audio Class 2 (UAC2) volume or mute requests to the corresponding + * static kcontrol bound to that UAC2 entity ID, supporting channel 0 (master) and + * individual channels. + * + * \param[in] entity_id UAC2 Feature Unit Entity ID. + * \param[in] channel Audio channel index (0 = master/all, 1 = first channel, etc.). + * \param[in] val Value from USB request (8.8 fixed-point dB volume or boolean mute). + * \param[in] is_volume True for volume command, false for mute switch command. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set_by_uac2(uint8_t entity_id, uint8_t channel, int32_t val, bool is_volume) +{ + const struct sof_static_topology *topo = sof_static_topology_get(); + + if (!topo) { + LOG_ERR("sof_static_kcontrol_set_by_uac2: no active topology"); + return -ENODEV; + } + + /* Search controls in active topology for matching UAC2 entity ID */ + for (size_t i = 0; i < topo->num_controls; i++) { + const struct sof_static_kcontrol *ctl = &topo->controls[i]; + + if (ctl->uac2_entity_id == entity_id) { + /* Validate channel bounds */ + if (channel > ctl->channels && channel != 0) { + LOG_ERR("sof_static_kcontrol_set_by_uac2: channel %u exceeds ctl channels %u", + channel, ctl->channels); + return -EINVAL; + } + + struct uac2_control_cache *cache = get_uac2_cache(entity_id, is_volume); + + if (is_volume && ctl->type == SOF_STATIC_CTRL_VOLUME) { + /* Translate UAC2 8.8 dB volume to linear SOF Q1.31 volume */ + int32_t sof_vol = uac2_to_sof_volume((int16_t)val); + int ret; + + if (channel == 0) { + /* Master channel: apply volume change to all channels first */ + ret = sof_static_kcontrol_set(ctl->id, sof_vol); + if (ret < 0) + return ret; + + /* Update cache only after the hardware/DSP operation succeeds */ + if (cache) { + for (uint32_t ch = 0; ch <= ctl->channels && ch <= SOF_IPC_MAX_CHANNELS; ch++) + cache->val[ch] = val; + } + return 0; + } + + /* Individual channel: apply volume change to specific channel first */ + ret = sof_static_kcontrol_set_chan(ctl->id, channel - 1, sof_vol); + if (ret < 0) + return ret; + + /* Update channel cache only after the hardware/DSP operation succeeds */ + if (cache && channel <= SOF_IPC_MAX_CHANNELS) + cache->val[channel] = val; + + return 0; + } else if (!is_volume && ctl->type == SOF_STATIC_CTRL_SWITCH) { + /* In UAC2: val=1 means MUTED, so enabled switch state is 0 */ + int32_t sof_sw = val ? 0 : 1; + int ret; + + if (channel == 0) { + /* Master channel: apply mute switch change to all channels first */ + ret = sof_static_kcontrol_set(ctl->id, sof_sw); + if (ret < 0) + return ret; + + /* Update cache only after the hardware/DSP operation succeeds */ + if (cache) { + for (uint32_t ch = 0; ch <= ctl->channels && ch <= SOF_IPC_MAX_CHANNELS; ch++) + cache->val[ch] = val; + } + return 0; + } + + /* Individual channel: apply mute switch change to specific channel first */ + ret = sof_static_kcontrol_set_chan(ctl->id, channel - 1, sof_sw); + if (ret < 0) + return ret; + + /* Update channel cache only after the hardware/DSP operation succeeds */ + if (cache && channel <= SOF_IPC_MAX_CHANNELS) + cache->val[channel] = val; + + return 0; + } + } + } + + LOG_ERR("sof_static_kcontrol_set_by_uac2: no control found for UAC2 entity %u", entity_id); + return -ENOENT; +} + +/** + * \brief Query kcontrol value formatted for USB Audio Class 2.0 response. + * + * \param[in] entity_id UAC2 Feature Unit Entity ID. + * \param[in] channel Audio channel index (0 = master, 1 = first channel, etc.). + * \param[out] val Pointer to store retrieved UAC2 value. + * \param[in] is_volume True for volume query, false for mute query. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_get_by_uac2(uint8_t entity_id, uint8_t channel, int32_t *val, bool is_volume) +{ + const struct sof_static_topology *topo = sof_static_topology_get(); + + if (!topo || !val) { + LOG_ERR("sof_static_kcontrol_get_by_uac2: invalid topology or null val pointer"); + return -EINVAL; + } + + /* Search controls in active topology for matching UAC2 entity ID */ + for (size_t i = 0; i < topo->num_controls; i++) { + const struct sof_static_kcontrol *ctl = &topo->controls[i]; + + if (ctl->uac2_entity_id == entity_id) { + /* Validate channel bounds */ + if (channel > ctl->channels && channel != 0) { + LOG_ERR("sof_static_kcontrol_get_by_uac2: channel %u exceeds ctl channels %u", + channel, ctl->channels); + return -EINVAL; + } + + /* Retrieve cached UAC2 value (exact 8.8 dB or mute state) */ + struct uac2_control_cache *cache = get_uac2_cache(entity_id, is_volume); + if (cache && channel <= SOF_IPC_MAX_CHANNELS) { + *val = cache->val[channel]; + return 0; + } + + /* Default if uninitialized: 0 dB for volume, 0 (unmuted) for switch */ + *val = 0; + return 0; + } + } + + LOG_ERR("sof_static_kcontrol_get_by_uac2: no control found for UAC2 entity %u", entity_id); + return -ENOENT; +} + +/** + * \brief Trigger pipeline start or stop associated with a UAC2 terminal ID. + * + * Finds the host streaming component bound to the given USB terminal ID + * and delegates to sof_static_pipeline_start() or sof_static_pipeline_stop(). + * + * \param[in] terminal_id Bound UAC2 Terminal Entity ID. + * \param[in] start True to start pipeline, false to pause/stop. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_trigger_by_uac2_term(uint8_t terminal_id, bool start) +{ + const struct sof_static_topology *topo = sof_static_topology_get(); + + if (!topo) { + LOG_ERR("sof_static_pipeline_trigger_by_uac2_term: no active topology"); + return -ENODEV; + } + + /* Find host component matching terminal_id */ + for (size_t i = 0; i < topo->num_comps; i++) { + const struct sof_static_comp *cdesc = &topo->comps[i]; + + if (cdesc->type == SOF_STATIC_COMP_HOST && cdesc->ep.usb.terminal_id == terminal_id) { + /* Delegate directly to modular start/stop routines */ + int ret = start ? sof_static_pipeline_start(cdesc->pipeline_id) : + sof_static_pipeline_stop(cdesc->pipeline_id); + + if (ret == 0) + LOG_INF("Pipeline %u %s via UAC2 terminal %u", cdesc->pipeline_id, + start ? "STARTED" : "STOPPED", terminal_id); + else + LOG_ERR("Pipeline %u %s via UAC2 terminal %u failed: %d", + cdesc->pipeline_id, start ? "start" : "stop", terminal_id, ret); + + return ret; + } + } + + LOG_ERR("sof_static_pipeline_trigger_by_uac2_term: no host component found for terminal %u", terminal_id); + return -ENOENT; +} diff --git a/src/audio/selector/selector.c b/src/audio/selector/selector.c index bc48f99b5126..10a6875e9733 100644 --- a/src/audio/selector/selector.c +++ b/src/audio/selector/selector.c @@ -1182,3 +1182,153 @@ SOF_MODULE_INIT(selector, sys_comp_module_selector_interface_init); #endif #endif + +#if CONFIG_STATIC_PIPELINE +#include +#include + +static struct comp_dev *sel_static_create(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ +#if CONFIG_IPC_MAJOR_4 + /* + * In IPC4 mode, selector module_adapter init expects + * struct sof_selector_avs_ipc4_config containing base_cfg and output_format. + * Synthesizing this structure satisfies selector_init size validation. + */ + struct sof_selector_avs_ipc4_config sel_cfg; + + memset(&sel_cfg, 0, sizeof(sel_cfg)); + sof_static_init_base_cfg(&sel_cfg.base_cfg, cdesc, period_us); + sel_cfg.output_format = sel_cfg.base_cfg.audio_fmt; + + if (cdesc->caps.sink_rate) + sel_cfg.output_format.sampling_frequency = cdesc->caps.sink_rate; + + struct ipc_config_process sel_spec = { + .size = sizeof(sel_cfg), + .data = (const uint8_t *)&sel_cfg, + }; + return drv->ops.create(drv, cfg, &sel_spec); +#else + uint16_t ch = cdesc->caps.max_channels ? cdesc->caps.max_channels : 2; + struct sof_sel_config sel_cfg = { + .in_channels_count = ch, + .out_channels_count = ch, + .sel_channel = 0, + }; + struct ipc_config_process sel_spec = { + .size = sizeof(sel_cfg), + .data = (const uint8_t *)&sel_cfg, + }; + return drv->ops.create(drv, cfg, &sel_spec); +#endif +} + +static int sel_static_apply_enum(struct comp_dev *dev, uint32_t channel, int32_t val) +{ + /* Validate component pointer */ + if (!dev) + return -EINVAL; + + struct processing_module *mod = comp_mod(dev); + + if (!mod) { + comp_err(dev, "sel_static_apply_enum: NULL processing module"); + return -EINVAL; + } + + struct comp_data *cd = module_get_private_data(mod); + + if (!cd) { + comp_err(dev, "sel_static_apply_enum: NULL private data"); + return -EINVAL; + } + + /* Validate selection value: must be non-negative */ + if (val < 0) { + comp_err(dev, "sel_static_apply_enum: negative sel_channel %d", val); + return -EINVAL; + } + + /* Query source producer channel count or fallback to configured input width */ + uint32_t src_channels = 0; + struct comp_buffer *src = comp_dev_get_first_data_producer(dev); + if (src) + src_channels = audio_stream_get_channels(&src->stream); + else + src_channels = cd->config.in_channels_count; + + /* Validate selection against hardware ceiling and input stream channel width */ + if ((uint32_t)val >= SEL_SOURCE_CHANNELS_MAX || (src_channels && (uint32_t)val >= src_channels)) { + comp_err(dev, "sel_static_apply_enum: invalid sel_channel %d (source channels %u)", + val, src_channels); + return -EINVAL; + } + + /* Apply validated channel selection to runtime configuration */ + cd->config.sel_channel = (uint32_t)val; + +#if CONFIG_IPC_MAJOR_4 + /* + * In IPC4 mode, routing is controlled by the Q10 coefficient matrix cd->coeffs_config. + * If channel indicates a single output channel (channel < out_channels_count), route + * input channel 'val' to that specific output channel. If channel indicates all + * channels (channel >= out_channels_count), route input channel 'val' to all output + * channels. + */ + uint32_t out_channels = cd->config.out_channels_count ? + cd->config.out_channels_count : SEL_SINK_CHANNELS_MAX; + + if (channel < out_channels) { + /* Route selected input channel to the specified output channel */ + for (uint32_t in_ch = 0; in_ch < SEL_SOURCE_CHANNELS_MAX; in_ch++) + cd->coeffs_config.coeffs[channel][in_ch] = (in_ch == (uint32_t)val) ? + SEL_COEF_ONE_Q10 : 0; + } else { + /* Route selected input channel to all output channels */ + for (uint32_t out_ch = 0; out_ch < out_channels && out_ch < SEL_SINK_CHANNELS_MAX; out_ch++) { + for (uint32_t in_ch = 0; in_ch < SEL_SOURCE_CHANNELS_MAX; in_ch++) + cd->coeffs_config.coeffs[out_ch][in_ch] = (in_ch == (uint32_t)val) ? + SEL_COEF_ONE_Q10 : 0; + } + } + + /* + * Re-evaluate passthrough flag: passthrough is true only when input and + * output channel counts match and coeffs form an identity matrix. + */ + if (cd->config.in_channels_count == cd->config.out_channels_count && + cd->config.in_channels_count > 0) { + cd->passthrough = true; + for (uint32_t i = 0; i < cd->config.out_channels_count; i++) { + for (uint32_t j = 0; j < cd->config.in_channels_count; j++) { + int16_t c = cd->coeffs_config.coeffs[i][j]; + + if ((i == j && c != SEL_COEF_ONE_Q10) || (i != j && c != 0)) { + cd->passthrough = false; + break; + } + } + if (!cd->passthrough) + break; + } + } else { + cd->passthrough = false; + } +#endif + + return 0; +} + +#if CONFIG_IPC_MAJOR_4 +static struct sof_static_module_ops sel4_static_ops = { + .uuid = &selector4_uuid, + .create = sel_static_create, + .apply_enum = sel_static_apply_enum, +}; +DECLARE_STATIC_MODULE_OPS(selector4, &sel4_static_ops); +#endif +#endif /* CONFIG_STATIC_PIPELINE */ diff --git a/src/audio/src/src.c b/src/audio/src/src.c index 9870ad911ca8..32470404f52d 100644 --- a/src/audio/src/src.c +++ b/src/audio/src/src.c @@ -107,4 +107,35 @@ SOF_LLEXT_BUILDINFO; DECLARE_MODULE_ADAPTER(src_interface, SRC_UUID, src_tr); SOF_MODULE_INIT(src, sys_comp_module_src_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +#if CONFIG_IPC_MAJOR_4 +static struct comp_dev *src_static_create(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ + struct ipc4_config_src src_cfg; + + memset(&src_cfg, 0, sizeof(src_cfg)); + sof_static_init_base_cfg(&src_cfg.base, cdesc, period_us); + src_cfg.sink_rate = cdesc->caps.sink_rate ? cdesc->caps.sink_rate : cdesc->caps.default_rate; + + struct ipc_config_process spec = { + .size = sizeof(src_cfg), + .data = (const uint8_t *)&src_cfg, + }; + return drv->ops.create(drv, cfg, &spec); +} + +static struct sof_static_module_ops src_static_ops = { + .uuid = &SRC_UUID, + .create = src_static_create, +}; + +DECLARE_STATIC_MODULE_OPS(src, &src_static_ops); +#endif /* CONFIG_IPC_MAJOR_4 */ +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/tdfb/tdfb.c b/src/audio/tdfb/tdfb.c index eed22f395632..6dc94409977f 100644 --- a/src/audio/tdfb/tdfb.c +++ b/src/audio/tdfb/tdfb.c @@ -945,4 +945,40 @@ DECLARE_TR_CTX(tdfb_tr, SOF_UUID(tdfb_uuid), LOG_LEVEL_INFO); DECLARE_MODULE_ADAPTER(tdfb_interface, tdfb_uuid, tdfb_tr); SOF_MODULE_INIT(tdfb, sys_comp_module_tdfb_interface_init); +#if CONFIG_STATIC_PIPELINE +#include + +static int tdfb_static_apply_switch(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) { + comp_err(dev, "tdfb_static_apply_switch: NULL processing module"); + return -EINVAL; + } + + struct tdfb_comp_data *cd = module_get_private_data(mod); + + if (!cd) { + comp_err(dev, "tdfb_static_apply_switch: NULL private data"); + return -EINVAL; + } + + /* + * Update beamforming enable state and flag that FIR setup needs + * to be recalculated in the next tdfb_process() cycle. + */ + cd->beam_on = (val != 0); + cd->update = true; + return 0; +} + +static struct sof_static_module_ops tdfb_static_ops = { + .uuid = &tdfb_uuid, + .apply_switch = tdfb_static_apply_switch, +}; + +DECLARE_STATIC_MODULE_OPS(tdfb, &tdfb_static_ops); +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/audio/volume/volume.c b/src/audio/volume/volume.c index 65d4d1e674e6..8dd2e55cc317 100644 --- a/src/audio/volume/volume.c +++ b/src/audio/volume/volume.c @@ -889,4 +889,170 @@ DECLARE_MODULE_ADAPTER(gain_interface, gain_uuid, gain_tr); SOF_MODULE_INIT(gain, sys_comp_module_gain_interface_init); #endif +#if CONFIG_STATIC_PIPELINE +#include + +#if CONFIG_IPC_MAJOR_4 +#include "peak_volume.h" + +struct static_ipc4_vol_init_cfg { + struct ipc4_base_module_cfg base_cfg; + struct ipc4_peak_volume_config config[1]; +}; +#endif + +static struct comp_dev *vol_static_create(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us) +{ + struct static_ipc4_vol_init_cfg vol_cfg; + + memset(&vol_cfg, 0, sizeof(vol_cfg)); + sof_static_init_base_cfg(&vol_cfg.base_cfg, cdesc, period_us); + vol_cfg.config[0].channel_id = 0xffffffff; + vol_cfg.config[0].target_volume = 0x7FFFFFFF; + vol_cfg.config[0].curve_type = IPC4_AUDIO_CURVE_TYPE_WINDOWS_FADE; + vol_cfg.config[0].curve_duration = 100000; + + struct ipc_config_process spec = { + .size = sizeof(vol_cfg), + .data = (const uint8_t *)&vol_cfg, + }; + return drv->ops.create(drv, cfg, &spec); +} + +static int vol_static_apply_volume(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + /* Validate component pointer */ + if (!dev) + return -EINVAL; + + /* Validate channels count against allowed maximum */ + if (channels == 0 || channels > SOF_IPC_MAX_CHANNELS) { + comp_err(dev, "vol_static_apply_volume: invalid channel count %u", channels); + return -EINVAL; + } + + struct processing_module *mod = comp_mod(dev); + + if (!mod) { + comp_err(dev, "vol_static_apply_volume: NULL processing module"); + return -EINVAL; + } + + /* Apply volume gain value to each specified channel */ + for (uint32_t ch = 0; ch < channels; ch++) + volume_set_chan(mod, ch, val, true); + + return 0; +} + +static int vol_static_apply_volume_chan(struct comp_dev *dev, uint32_t channel, int32_t val) +{ + /* Validate component pointer */ + if (!dev) + return -EINVAL; + + /* Validate channel index against allowed maximum */ + if (channel >= SOF_IPC_MAX_CHANNELS) { + comp_err(dev, "vol_static_apply_volume_chan: invalid channel %u", channel); + return -EINVAL; + } + + struct processing_module *mod = comp_mod(dev); + + if (!mod) { + comp_err(dev, "vol_static_apply_volume_chan: NULL processing module"); + return -EINVAL; + } + + /* Apply volume gain value to single specified channel */ + volume_set_chan(mod, channel, val, true); + return 0; +} + +static int vol_static_apply_switch(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + /* Validate component pointer */ + if (!dev) + return -EINVAL; + + /* Validate channels count against allowed maximum */ + if (channels == 0 || channels > SOF_IPC_MAX_CHANNELS) { + comp_err(dev, "vol_static_apply_switch: invalid channel count %u", channels); + return -EINVAL; + } + + struct processing_module *mod = comp_mod(dev); + + if (!mod) { + comp_err(dev, "vol_static_apply_switch: NULL processing module"); + return -EINVAL; + } + + /* Apply mute/unmute state to each specified channel */ + for (uint32_t ch = 0; ch < channels; ch++) { + if (val == 0) + volume_set_chan_mute(mod, ch); + else + volume_set_chan_unmute(mod, ch); + } + + return 0; +} + +static int vol_static_apply_switch_chan(struct comp_dev *dev, uint32_t channel, int32_t val) +{ + /* Validate component pointer */ + if (!dev) + return -EINVAL; + + /* Validate channel index against allowed maximum */ + if (channel >= SOF_IPC_MAX_CHANNELS) { + comp_err(dev, "vol_static_apply_switch_chan: invalid channel %u", channel); + return -EINVAL; + } + + struct processing_module *mod = comp_mod(dev); + + if (!mod) { + comp_err(dev, "vol_static_apply_switch_chan: NULL processing module"); + return -EINVAL; + } + + /* Apply mute/unmute state to single specified channel */ + if (val == 0) + volume_set_chan_mute(mod, channel); + else + volume_set_chan_unmute(mod, channel); + + return 0; +} + +static struct sof_static_module_ops vol_static_ops = { + .uuid = &volume_uuid, + .create = vol_static_create, + .apply_volume = vol_static_apply_volume, + .apply_volume_chan = vol_static_apply_volume_chan, + .apply_switch = vol_static_apply_switch, + .apply_switch_chan = vol_static_apply_switch_chan, +}; + +DECLARE_STATIC_MODULE_OPS(volume, &vol_static_ops); + +#if CONFIG_COMP_GAIN +static struct sof_static_module_ops gain_static_ops = { + .uuid = &gain_uuid, + .create = vol_static_create, + .apply_volume = vol_static_apply_volume, + .apply_volume_chan = vol_static_apply_volume_chan, + .apply_switch = vol_static_apply_switch, + .apply_switch_chan = vol_static_apply_switch_chan, +}; + +DECLARE_STATIC_MODULE_OPS(gain, &gain_static_ops); +#endif +#endif /* CONFIG_STATIC_PIPELINE */ + #endif diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index 48d0764f26d8..69c47d1c54c7 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -1021,6 +1021,19 @@ void comp_unregister(struct comp_driver_info *drv); */ int comp_set_adapter_ops(const struct comp_driver *drv, const struct module_interface *ops); +/** + * Look up a registered SOF component driver by UUID or driver type. + * + * Scans the global SOF component driver registry to match either a specific + * 128-bit UUID (for audio processing modules) or a generic driver type (for host + * or DAI endpoints). + * + * @param uuid Component UUID to match, or NULL to match by type. + * @param type SOF component driver type (SOF_COMP_HOST, SOF_COMP_DAI, etc.). + * @return Pointer to matched comp_driver, or NULL if not registered. + */ +const struct comp_driver *comp_driver_find(const struct sof_uuid *uuid, uint32_t type); + /** @}*/ /** diff --git a/src/include/sof/audio/pipeline/static_pipeline.h b/src/include/sof/audio/pipeline/static_pipeline.h new file mode 100644 index 000000000000..6c9a36f3c319 --- /dev/null +++ b/src/include/sof/audio/pipeline/static_pipeline.h @@ -0,0 +1,887 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2026 Sound Open Firmware (SOF) Project + */ + +/** + * \file include/sof/audio/pipeline/static_pipeline.h + * \brief Declarative Static Audio Pipeline Engine API + * \author Liam Girdwood + * + * \defgroup static_pipeline_api Static Audio Pipeline Engine + * @{ + * + * The Static Audio Pipeline subsystem provides compile-time and runtime + * declarative topology construction for hostless, microcontroller, and + * standalone embedded platforms running Sound Open Firmware. + * + * It allows platforms to construct complete SOF processing graphs (pipelines, + * modules, DAIs, intermediate buffers, routes, and kcontrols) directly at boot + * without requiring dynamic IPC topology commands from a host operating system. + */ + +#ifndef __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_H__ +#define __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ========================================================================= + * Audio Format & Pipeline Capabilities + * ========================================================================= */ + +/** \brief Supported sample rate bitmasks for static capability negotiation */ +#define SOF_STATIC_RATE_8K SOF_RATE_8000 /**< 8000 Hz */ +#define SOF_STATIC_RATE_11K SOF_RATE_11025 /**< 11025 Hz */ +#define SOF_STATIC_RATE_12K SOF_RATE_12000 /**< 12000 Hz */ +#define SOF_STATIC_RATE_16K SOF_RATE_16000 /**< 16000 Hz */ +#define SOF_STATIC_RATE_22K SOF_RATE_22050 /**< 22050 Hz */ +#define SOF_STATIC_RATE_24K SOF_RATE_24000 /**< 24000 Hz */ +#define SOF_STATIC_RATE_32K SOF_RATE_32000 /**< 32000 Hz */ +#define SOF_STATIC_RATE_44K1 SOF_RATE_44100 /**< 44100 Hz */ +#define SOF_STATIC_RATE_48K SOF_RATE_48000 /**< 48000 Hz */ +#define SOF_STATIC_RATE_64K SOF_RATE_64000 /**< 64000 Hz */ +#define SOF_STATIC_RATE_88K2 SOF_RATE_88200 /**< 88200 Hz */ +#define SOF_STATIC_RATE_96K SOF_RATE_96000 /**< 96000 Hz */ +#define SOF_STATIC_RATE_176K4 SOF_RATE_176400 /**< 176400 Hz */ +#define SOF_STATIC_RATE_192K SOF_RATE_192000 /**< 192000 Hz */ +#define SOF_STATIC_RATE_384K (1 << 14) /**< 384000 Hz */ + +/** \brief Supported PCM frame format bitmasks (matching enum sof_ipc_frame) */ +#define SOF_STATIC_FMT_S16_LE BIT(SOF_IPC_FRAME_S16_LE) +#define SOF_STATIC_FMT_S24_4LE BIT(SOF_IPC_FRAME_S24_4LE) +#define SOF_STATIC_FMT_S32_LE BIT(SOF_IPC_FRAME_S32_LE) +#define SOF_STATIC_FMT_FLOAT BIT(SOF_IPC_FRAME_FLOAT) +#define SOF_STATIC_FMT_S24_3LE BIT(SOF_IPC_FRAME_S24_3LE) +#define SOF_STATIC_FMT_S24_4LE_MSB BIT(SOF_IPC_FRAME_S24_4LE_MSB) +#define SOF_STATIC_FMT_U8 BIT(SOF_IPC_FRAME_U8) +#define SOF_STATIC_FMT_S16_4LE BIT(SOF_IPC_FRAME_S16_4LE) +#define SOF_STATIC_FMT_A_LAW BIT(SOF_IPC_FRAME_A_LAW) +#define SOF_STATIC_FMT_MU_LAW BIT(SOF_IPC_FRAME_MU_LAW) + +/** Mask covering all supported sample rates */ +#define SOF_STATIC_RATE_MASK_ALL \ + (SOF_STATIC_RATE_8K | SOF_STATIC_RATE_11K | SOF_STATIC_RATE_12K | \ + SOF_STATIC_RATE_16K | SOF_STATIC_RATE_22K | SOF_STATIC_RATE_24K | \ + SOF_STATIC_RATE_32K | SOF_STATIC_RATE_44K1 | SOF_STATIC_RATE_48K | \ + SOF_STATIC_RATE_64K | SOF_STATIC_RATE_88K2 | SOF_STATIC_RATE_96K | \ + SOF_STATIC_RATE_176K4 | SOF_STATIC_RATE_192K | SOF_STATIC_RATE_384K) + +/** Mask covering all supported frame formats */ +#define SOF_STATIC_FMT_MASK_ALL \ + (SOF_STATIC_FMT_S16_LE | SOF_STATIC_FMT_S24_4LE | SOF_STATIC_FMT_S32_LE | \ + SOF_STATIC_FMT_FLOAT | SOF_STATIC_FMT_S24_3LE | SOF_STATIC_FMT_S24_4LE_MSB | \ + SOF_STATIC_FMT_U8 | SOF_STATIC_FMT_S16_4LE | SOF_STATIC_FMT_A_LAW | \ + SOF_STATIC_FMT_MU_LAW) + +/** + * \brief Audio stream format and sample rate capability descriptor. + */ +struct sof_static_caps { + uint32_t formats; /**< Bitmask of supported frame formats (SOF_STATIC_FMT_*) */ + uint32_t rates; /**< Bitmask of supported sample rates (SOF_STATIC_RATE_*) */ + uint16_t min_channels; /**< Minimum channel count */ + uint16_t max_channels; /**< Maximum channel count */ + uint32_t default_rate; /**< Default sample rate in Hz (e.g. 48000) */ + uint32_t sink_rate; /**< Target sink sample rate for converters (SRC/ASRC, 0 = same as default_rate) */ + enum sof_ipc_frame default_fmt; /**< Default frame format */ +}; + +/** + * \brief Helper macro to initialize a static capability structure. + * \param _fmt Frame format (e.g. SOF_IPC_FRAME_S16_LE or SOF_IPC_FRAME_FLOAT). + * \param _rate Default sample rate in Hz (e.g. 48000). + * \param _ch Number of audio channels (e.g. 2). + */ +#define SOF_STATIC_CAPS(_fmt, _rate, _ch) \ + { \ + .formats = BIT(_fmt), \ + .rates = (_rate == 8000) ? SOF_STATIC_RATE_8K : \ + (_rate == 11025) ? SOF_STATIC_RATE_11K : \ + (_rate == 12000) ? SOF_STATIC_RATE_12K : \ + (_rate == 16000) ? SOF_STATIC_RATE_16K : \ + (_rate == 22050) ? SOF_STATIC_RATE_22K : \ + (_rate == 24000) ? SOF_STATIC_RATE_24K : \ + (_rate == 32000) ? SOF_STATIC_RATE_32K : \ + (_rate == 44100) ? SOF_STATIC_RATE_44K1 : \ + (_rate == 48000) ? SOF_STATIC_RATE_48K : \ + (_rate == 64000) ? SOF_STATIC_RATE_64K : \ + (_rate == 88200) ? SOF_STATIC_RATE_88K2 : \ + (_rate == 96000) ? SOF_STATIC_RATE_96K : \ + (_rate == 176400) ? SOF_STATIC_RATE_176K4 : \ + (_rate == 192000) ? SOF_STATIC_RATE_192K : \ + (_rate == 384000) ? SOF_STATIC_RATE_384K : SOF_STATIC_RATE_48K, \ + .min_channels = (_ch), \ + .max_channels = (_ch), \ + .default_rate = (_rate), \ + .default_fmt = (_fmt), \ + } + +/* ========================================================================= + * Components / Processing Modules + * ========================================================================= */ + +/** + * \brief Category of a static pipeline component. + */ +enum sof_static_comp_type { + SOF_STATIC_COMP_MODULE = 0, /**< Audio processing module (Volume, EQ, DRC, etc.) */ + SOF_STATIC_COMP_HOST, /**< Host or USB streaming endpoint */ + SOF_STATIC_COMP_DAI, /**< Hardware Digital Audio Interface (I2S, PDM, SAI, SPDIF) */ +}; + +/** + * \brief Hardware endpoint binding type. + */ +enum sof_static_ep_type { + SOF_STATIC_EP_NONE = 0, /**< Standard internal processing module */ + SOF_STATIC_EP_USB_TERMINAL, /**< USB Audio Class 2 (UAC2) streaming terminal */ + SOF_STATIC_EP_DAI, /**< Physical hardware DAI controller */ +}; + +/** + * \brief Declarative component descriptor. + */ +struct sof_static_comp { + uint32_t id; /**< Unique component identifier */ + uint32_t pipeline_id; /**< Owning pipeline identifier */ + const char *name; /**< Human-readable diagnostic name (e.g. "VOL_PB") */ + enum sof_static_comp_type type; /**< Component category: Module, Host, or DAI */ + const struct sof_uuid *uuid; /**< Registered driver UUID (NULL for standard Host or DAI) */ + uint32_t direction; /**< Audio stream direction: SOF_IPC_STREAM_PLAYBACK or CAPTURE */ + struct sof_static_caps caps; /**< Format and sample rate capabilities */ + + enum sof_static_ep_type ep_type; /**< Endpoint binding type */ + union { + struct { + uint32_t terminal_id; /**< Bound UAC2 USB Terminal Entity ID */ + } usb; + struct { + uint32_t dai_type; /**< DAI type (e.g. SOF_DAI_ESP32_I2S, SOF_DAI_IMX_SAI) */ + uint32_t dai_index; /**< DAI hardware instance index */ + uint32_t format; /**< Protocol/clock format mask (SOF_DAI_FMT_*) */ + int (*configure)(struct comp_dev *dev, + struct ipc_config_dai *dai_cfg, + void *spec_cfg); /**< Optional platform DAI endpoint config hook */ + } dai; + } ep; + + const void *init_blob; /**< Optional initial ABI configuration blob */ + size_t init_blob_size; /**< Size of initial configuration blob in bytes */ +}; + +/** + * \brief Declare a processing module component. + */ +#define SOF_STATIC_COMP_MODULE(...) \ + { .type = SOF_STATIC_COMP_MODULE, __VA_ARGS__ } + +/** + * \brief Declare a USB/Host streaming endpoint component. + */ +#define SOF_STATIC_COMP_HOST(...) \ + { .type = SOF_STATIC_COMP_HOST, .ep_type = SOF_STATIC_EP_USB_TERMINAL, __VA_ARGS__ } + +/** + * \brief Declare a hardware DAI endpoint component. + */ +#define SOF_STATIC_COMP_DAI(...) \ + { .type = SOF_STATIC_COMP_DAI, .ep_type = SOF_STATIC_EP_DAI, __VA_ARGS__ } + +/* ========================================================================= + * Module Constructor Macros + * ========================================================================= */ + +/** + * \brief Generic constructor macro for an audio processing module component. + * + * \param _id Unique component ID. + * \param _ppl Owning pipeline ID. + * \param _name Human-readable component name string. + * \param _dir Stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param _uuid Pointer to component driver UUID (e.g. &volume_uuid). + * \param _fmt Default PCM frame format (enum sof_ipc_frame). + * \param _rate Default stream sample rate in Hz. + * \param _ch Default channel count. + * \param _blob Optional pointer to initial configuration blob (or NULL). + * \param _blob_sz Size of initial configuration blob in bytes (or 0). + */ +#define SOF_STATIC_MODULE(_id, _ppl, _name, _dir, _uuid, _fmt, _rate, _ch, _blob, _blob_sz) \ + { \ + .id = (_id), \ + .pipeline_id = (_ppl), \ + .name = (_name), \ + .type = SOF_STATIC_COMP_MODULE, \ + .uuid = (_uuid), \ + .direction = (_dir), \ + .caps = SOF_STATIC_CAPS((_fmt), (_rate), (_ch)), \ + .init_blob = (_blob), \ + .init_blob_size = (_blob_sz), \ + } + +/** + * \brief Generic constructor macro for a sample rate converter component (SRC / ASRC). + * + * \param _id Unique component ID. + * \param _ppl Owning pipeline ID. + * \param _name Human-readable component name string. + * \param _dir Stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param _uuid Pointer to component driver UUID (e.g. &src_uuid, &asrc_uuid). + * \param _fmt Default PCM frame format (enum sof_ipc_frame). + * \param _in_rate Source/input sample rate in Hz. + * \param _out_rate Sink/output sample rate in Hz. + * \param _ch Channel count. + */ +#define SOF_STATIC_MODULE_RATE_CONV(_id, _ppl, _name, _dir, _uuid, _fmt, _in_rate, _out_rate, _ch) \ + { \ + .id = (_id), \ + .pipeline_id = (_ppl), \ + .name = (_name), \ + .type = SOF_STATIC_COMP_MODULE, \ + .uuid = (_uuid), \ + .direction = (_dir), \ + .caps = { \ + .formats = BIT(_fmt), \ + .rates = SOF_STATIC_RATE_MASK_ALL, \ + .min_channels = (_ch), \ + .max_channels = (_ch), \ + .default_rate = (_in_rate), \ + .sink_rate = (_out_rate), \ + .default_fmt = (_fmt), \ + }, \ + } + +/* ========================================================================= + * Endpoint Constructor Macros + * ========================================================================= */ + +/** + * \brief Simple constructor macro for a USB / UAC2 streaming endpoint. + * + * \param _id Unique component ID. + * \param _ppl Owning pipeline ID. + * \param _name Human-readable component name. + * \param _dir Stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param _fmt Default PCM frame format (enum sof_ipc_frame). + * \param _rate Stream sample rate in Hz. + * \param _ch Channel count. + * \param _term_id USB Audio Class Terminal ID. + */ +#define SOF_STATIC_ENDPOINT_USB(_id, _ppl, _name, _dir, _fmt, _rate, _ch, _term_id) \ + { \ + .id = (_id), \ + .pipeline_id = (_ppl), \ + .name = (_name), \ + .type = SOF_STATIC_COMP_HOST, \ + .direction = (_dir), \ + .caps = SOF_STATIC_CAPS((_fmt), (_rate), (_ch)), \ + .ep_type = SOF_STATIC_EP_USB_TERMINAL, \ + .ep.usb.terminal_id = (_term_id), \ + } + +/** + * \brief Generic constructor macro for a hardware Digital Audio Interface (DAI) endpoint. + * + * \param _id Unique component ID. + * \param _ppl Owning pipeline ID. + * \param _name Human-readable component name. + * \param _dir Stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param _fmt Default PCM frame format (enum sof_ipc_frame). + * \param _rate Stream sample rate in Hz. + * \param _ch Channel count. + * \param _type Hardware DAI type (e.g. SOF_DAI_INTEL_SSP, SOF_DAI_ESP32_I2S, etc.). + * \param _idx Hardware DAI controller instance index. + * \param _clk_fmt DAI clock format flags (e.g. SOF_DAI_FMT_I2S | SOF_DAI_FMT_CBC_CFC). + */ +#define SOF_STATIC_ENDPOINT_DAI(_id, _ppl, _name, _dir, _fmt, _rate, _ch, _type, _idx, _clk_fmt) \ + { \ + .id = (_id), \ + .pipeline_id = (_ppl), \ + .name = (_name), \ + .type = SOF_STATIC_COMP_DAI, \ + .direction = (_dir), \ + .caps = SOF_STATIC_CAPS((_fmt), (_rate), (_ch)), \ + .ep_type = SOF_STATIC_EP_DAI, \ + .ep.dai.dai_type = (_type), \ + .ep.dai.dai_index = (_idx), \ + .ep.dai.format = (_clk_fmt), \ + } + +/** + * \brief Constructor macro for a hardware DAI endpoint with custom platform configuration callback. + * + * \param _id Unique component ID. + * \param _ppl Owning pipeline ID. + * \param _name Human-readable component name. + * \param _dir Stream direction (SOF_IPC_STREAM_PLAYBACK or SOF_IPC_STREAM_CAPTURE). + * \param _fmt Default PCM frame format (enum sof_ipc_frame). + * \param _rate Stream sample rate in Hz. + * \param _ch Channel count. + * \param _type Hardware DAI type (e.g. SOF_DAI_INTEL_SSP, SOF_DAI_ESP32_I2S, etc.). + * \param _idx Hardware DAI controller instance index. + * \param _clk_fmt DAI clock format flags (e.g. SOF_DAI_FMT_I2S | SOF_DAI_FMT_CBC_CFC). + * \param _cfg_fn Endpoint configuration callback function pointer. + */ +#define SOF_STATIC_ENDPOINT_DAI_CFG(_id, _ppl, _name, _dir, _fmt, _rate, _ch, _type, _idx, _clk_fmt, _cfg_fn) \ + { \ + .id = (_id), \ + .pipeline_id = (_ppl), \ + .name = (_name), \ + .type = SOF_STATIC_COMP_DAI, \ + .direction = (_dir), \ + .caps = SOF_STATIC_CAPS((_fmt), (_rate), (_ch)), \ + .ep_type = SOF_STATIC_EP_DAI, \ + .ep.dai.dai_type = (_type), \ + .ep.dai.dai_index = (_idx), \ + .ep.dai.format = (_clk_fmt), \ + .ep.dai.configure = (_cfg_fn), \ + } + +/* ========================================================================= + * Static Module Operations + * ========================================================================= */ + +/** + * \brief Static pipeline module driver operations. + * + * Audio processing modules register these operations to handle static + * instantiation (synthesizing any module-specific IPC init configuration) + * and runtime kcontrol manipulation (volume, switch/mute, enum) without + * requiring the generic pipeline loader to contain module-specific logic. + */ +struct sof_static_module_ops { + /** UUID of target audio component */ + const struct sof_uuid *uuid; + + /** + * \brief Instantiate and initialize the module component. + * \param[in] drv SOF component driver. + * \param[in] cfg IPC component configuration. + * \param[in] cdesc Static component descriptor. + * \param[in] period_us Owning pipeline scheduling period in microseconds. + * \return Created component device pointer, or NULL on error. + */ + struct comp_dev *(*create)(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us); + + /** + * \brief Apply linear volume gain to component. + * \param[in] dev Component device pointer. + * \param[in] channels Channel count to apply volume across. + * \param[in] val Linear volume value (0 to INT32_MAX). + * \return 0 on success, negative error code on failure. + */ + int (*apply_volume)(struct comp_dev *dev, uint32_t channels, int32_t val); + + /** + * \brief Apply linear volume gain to a specific channel. + * \param[in] dev Component device pointer. + * \param[in] channel Channel index (0-based). + * \param[in] val Linear volume value (0 to INT32_MAX). + * \return 0 on success, negative error code on failure. + */ + int (*apply_volume_chan)(struct comp_dev *dev, uint32_t channel, int32_t val); + + /** + * \brief Apply binary switch/mute state to component. + * \param[in] dev Component device pointer. + * \param[in] channels Channel count. + * \param[in] val Binary switch value (0 = muted/bypass, 1 = unmuted/active). + * \return 0 on success, negative error code on failure. + */ + int (*apply_switch)(struct comp_dev *dev, uint32_t channels, int32_t val); + + /** + * \brief Apply binary switch/mute state to a specific channel. + * \param[in] dev Component device pointer. + * \param[in] channel Channel index (0-based). + * \param[in] val Binary switch value (0 = muted/bypass, 1 = unmuted/active). + * \return 0 on success, negative error code on failure. + */ + int (*apply_switch_chan)(struct comp_dev *dev, uint32_t channel, int32_t val); + + /** + * \brief Apply enumerated route/value to component. + * \param[in] dev Component device pointer. + * \param[in] channel Channel index. + * \param[in] val Selected enumeration value. + * \return 0 on success, negative error code on failure. + */ + int (*apply_enum)(struct comp_dev *dev, uint32_t channel, int32_t val); + + /** + * \brief Apply raw binary configuration or coefficient blob to component. + * \param[in] dev Component device pointer. + * \param[in] data Pointer to binary data blob. + * \param[in] size Size of binary data in bytes. + * \return 0 on success, negative error code on failure. + */ + int (*apply_binary)(struct comp_dev *dev, const void *data, size_t size); + + /** List node for registered ops */ + struct list_item list; +}; + +/** + * \brief Macro to automatically register static module operations at system startup. + * + * In Zephyr RTOS environments, registers using SYS_INIT at APPLICATION level. + * In non-Zephyr environments (e.g. POSIX tests), registers using constructor attribute. + * + * \param name Unique identifier suffix for initialization routine. + * \param ops Pointer to struct sof_static_module_ops. + */ +#if defined(__ZEPHYR__) +#include +#define DECLARE_STATIC_MODULE_OPS(name, ops) \ + static int _static_ops_init_##name(void) \ + { \ + return sof_static_register_module_ops(ops); \ + } \ + SYS_INIT(_static_ops_init_##name, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY) +#else +#define DECLARE_STATIC_MODULE_OPS(name, ops) \ + static __attribute__((constructor)) void _static_ops_init_##name(void) \ + { \ + sof_static_register_module_ops(ops); \ + } +#endif + +/* ========================================================================= + * Intermediate Audio Buffers & Pipeline Routing + * ========================================================================= */ + +/** + * \brief Declarative intermediate audio buffer descriptor. + */ +struct sof_static_buffer { + uint32_t id; /**< Unique buffer identifier */ + size_t size; /**< Total buffer allocation size in bytes */ + enum sof_ipc_frame fmt; /**< Buffer audio frame format (SOF_IPC_FRAME_INVALID = inherit from producer) */ + uint32_t rate; /**< Sample rate in Hz (0 = inherit from producer component) */ + uint16_t channels; /**< Channel count (0 = inherit from producer component) */ + uint32_t flags; /**< Memory allocation flags (e.g. SOF_MEM_FLAG_DMA) */ +}; + +/** + * \brief Helper macro to declare an intermediate audio buffer with standard flags. + * + * Initializes memory allocation flags to standard DMA and user memory, and defaults + * the format to SOF_IPC_FRAME_INVALID to inherit the PCM format from the upstream producer. + */ +#define SOF_STATIC_BUFFER(...) \ + { .flags = SOF_MEM_FLAG_DMA | SOF_MEM_FLAG_USER, .fmt = SOF_IPC_FRAME_INVALID, __VA_ARGS__ } + +/** + * \brief Declarative graph route connecting an upstream component to a downstream component. + */ +struct sof_static_route { + uint32_t src_comp_id; /**< Upstream producer component ID */ + uint32_t buffer_id; /**< Intermediate buffer ID linking the components */ + uint32_t sink_comp_id; /**< Downstream consumer component ID */ +}; + +/** + * \brief Helper macro to declare a connection route. + */ +#define SOF_STATIC_ROUTE(...) \ + { __VA_ARGS__ } + +/* ========================================================================= + * PCMs (Pulse Code Modulated Endpoints) + * ========================================================================= */ + +/** + * \brief Declarative PCM endpoint descriptor. + */ +struct sof_static_pcm { + uint32_t pcm_id; /**< Logical PCM stream index */ + const char *name; /**< Human-readable PCM name (e.g. "Speaker Playback") */ + uint32_t direction; /**< Audio direction: SOF_IPC_STREAM_PLAYBACK or CAPTURE */ + uint32_t pipeline_id; /**< Associated pipeline ID */ + uint32_t host_comp_id; /**< Host/USB endpoint component ID */ + struct sof_static_caps caps; /**< Supported PCM capabilities */ +}; + +/** + * \brief Helper macro to declare a static PCM stream. + */ +#define SOF_STATIC_PCM(...) \ + { __VA_ARGS__ } + +/* ========================================================================= + * Kcontrols (Volume, Mute, Bypass, Presets, Coefficients) + * ========================================================================= */ + +/** + * \brief Type of static kcontrol. + */ +enum sof_static_ctrl_type { + SOF_STATIC_CTRL_VOLUME = 0, /**< Linear or dB volume fader */ + SOF_STATIC_CTRL_SWITCH, /**< Boolean mute or module bypass switch */ + SOF_STATIC_CTRL_ENUM, /**< Multi-value enumeration selector */ + SOF_STATIC_CTRL_BINARY, /**< Raw binary parameter or coefficient blob */ +}; + +/** + * \brief Declarative static kcontrol descriptor. + */ +struct sof_static_kcontrol { + uint32_t id; /**< Unique control identifier */ + const char *name; /**< Human-readable control name (e.g. "Master Volume") */ + enum sof_static_ctrl_type type; /**< Control type */ + uint32_t target_comp_id; /**< Target component ID affected by this control (0 for custom) */ + uint32_t param_id; /**< Optional parameter or command index */ + + int32_t min; /**< Minimum valid value */ + int32_t max; /**< Maximum valid value */ + int32_t def; /**< Default initial value applied at boot */ + uint32_t channels; /**< Number of channels affected by this control */ + + uint8_t uac2_entity_id; /**< Bound external entity ID (e.g. UAC2 Feature Unit) */ +}; + +/** \brief Helper macro to declare a volume kcontrol */ +#define SOF_STATIC_KCONTROL_VOLUME(...) \ + { .type = SOF_STATIC_CTRL_VOLUME, __VA_ARGS__ } + +/** \brief Helper macro to declare a boolean switch/bypass kcontrol */ +#define SOF_STATIC_KCONTROL_SWITCH(...) \ + { .type = SOF_STATIC_CTRL_SWITCH, __VA_ARGS__ } + +/** \brief Helper macro to declare an enumeration selector kcontrol */ +#define SOF_STATIC_KCONTROL_ENUM(...) \ + { .type = SOF_STATIC_CTRL_ENUM, __VA_ARGS__ } + +/** \brief Helper macro to declare a binary configuration kcontrol */ +#define SOF_STATIC_KCONTROL_BINARY(...) \ + { .type = SOF_STATIC_CTRL_BINARY, __VA_ARGS__ } + +/* ========================================================================= + * Pipeline Descriptors & Top-Level Topology + * ========================================================================= */ + +/** + * \brief Declarative audio pipeline descriptor. + */ +struct sof_static_pipeline_desc { + uint32_t pipeline_id; /**< Unique pipeline identifier */ + const char *name; /**< Human-readable pipeline name */ + uint32_t direction; /**< Audio direction: SOF_IPC_STREAM_PLAYBACK or CAPTURE */ + uint32_t priority; /**< Scheduling priority (0 = normal) */ + uint32_t core; /**< Core affinity index (0, 1, ...) */ + uint32_t period; /**< Scheduling period in microseconds (e.g. 1000) */ + uint32_t frames_per_sched; /**< Number of frames processed per schedule tick */ + uint32_t time_domain; /**< Scheduling time domain (SOF_TIME_DOMAIN_TIMER or DMA) */ + uint32_t default_rate; /**< Default stream rate in Hz (e.g. 48000, 0 = auto) */ + uint16_t default_channels; /**< Default channel count (e.g. 2, 0 = auto) */ + uint32_t sched_comp_id; /**< Component driving pipeline scheduling */ + uint32_t source_comp_id; /**< Primary source endpoint component ID */ + uint32_t sink_comp_id; /**< Primary sink endpoint component ID */ +}; + +/** + * \brief Custom kcontrol callback handler type. + * + * Allows platforms or applications to register custom kcontrol dispatch + * logic for non-module controls (e.g. clock modes, routes, injector switches). + * + * \param[in] ctl Pointer to the static kcontrol descriptor. + * \param[in] val Value to write. + * \param[in,out] priv Private user/platform context. + * \return 0 on success, negative errno on failure. + */ +typedef int (*sof_static_kcontrol_handler_fn)(const struct sof_static_kcontrol *ctl, + int32_t val, void *priv); + +/** + * \brief Top-level static audio topology descriptor. + */ +struct sof_static_topology { + const char *name; /**< Human-readable topology name */ + size_t num_pipelines; /**< Number of pipelines in this topology */ + const struct sof_static_pipeline_desc *pipelines; /**< Array of pipeline descriptors */ + size_t num_comps; /**< Number of components */ + const struct sof_static_comp *comps; /**< Array of component descriptors */ + size_t num_buffers; /**< Number of intermediate buffers */ + const struct sof_static_buffer *buffers; /**< Array of buffer descriptors */ + size_t num_routes; /**< Number of graph connection routes */ + const struct sof_static_route *routes; /**< Array of connection routes */ + size_t num_pcms; /**< Number of logical PCMs */ + const struct sof_static_pcm *pcms; /**< Array of PCM descriptors */ + size_t num_controls; /**< Number of kcontrols */ + const struct sof_static_kcontrol *controls; /**< Array of kcontrol descriptors */ + + sof_static_kcontrol_handler_fn custom_control_handler; /**< Optional platform control hook */ + void *custom_control_data; /**< Platform context passed to hook */ +}; + +/* ========================================================================= + * Generic Engine Public APIs + * ========================================================================= */ + +/** + * \brief Initialize and instantiate the static topology graph. + * + * Allocates pipelines, instantiates components from the driver registry, + * initializes intermediate buffers, establishes graph connections, negotiates + * component stream parameters, and prepares all pipelines for streaming. + * + * \param[in] topo Pointer to the target static topology definition. + * \return 0 on success, negative errno on failure. + */ +int sof_static_topology_init(const struct sof_static_topology *topo); + +/** + * \brief Retrieve the currently active static topology descriptor. + * \return Pointer to active topology descriptor, or NULL if uninitialized. + */ +const struct sof_static_topology *sof_static_topology_get(void); + +/** + * \brief Retrieve a pipeline instance by its pipeline ID. + * \param[in] pipeline_id Unique pipeline ID. + * \return Pointer to struct pipeline, or NULL if not found. + */ +struct pipeline *sof_static_pipeline_get(uint32_t pipeline_id); + +/** + * \brief Retrieve a component device instance by its component ID. + * \param[in] comp_id Unique component ID. + * \return Pointer to struct comp_dev, or NULL if not found. + */ +struct comp_dev *sof_static_comp_get(uint32_t comp_id); + +/** + * \brief Retrieve an intermediate buffer instance by its buffer ID. + * \param[in] buffer_id Unique buffer ID. + * \return Pointer to struct comp_buffer, or NULL if not found. + */ +struct comp_buffer *sof_static_buffer_get(uint32_t buffer_id); + +/** + * \brief Register static module operations for an audio component. + * + * Audio processing modules register static operations at boot or module load + * time to handle static component instantiation and kcontrol dispatch without + * hardcoded component logic in the generic pipeline engine. + * + * \param[in,out] ops Pointer to module operations structure. + * \return 0 on success, negative error code on failure. + */ +int sof_static_register_module_ops(struct sof_static_module_ops *ops); + +/** + * \brief Find registered static module operations by component UUID. + * \param[in] uuid UUID of target component. + * \return Pointer to registered ops, or NULL if not found. + */ +const struct sof_static_module_ops *sof_static_find_module_ops(const struct sof_uuid *uuid); + +struct ipc4_base_module_cfg; + +/** + * \brief Synthesize standard IPC4 base module configuration structure. + * \param[out] base_cfg Target base configuration to populate. + * \param[in] cdesc Static component descriptor with format and rate capabilities. + * \param[in] period_us Pipeline scheduling period in microseconds. + */ +void sof_static_init_base_cfg(struct ipc4_base_module_cfg *base_cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us); + +/** + * \brief Default fallback creation for standard IPC4 module adapters. + * \param[in] drv SOF component driver. + * \param[in] cfg IPC component configuration. + * \param[in] cdesc Static component descriptor. + * \param[in] period_us Owning pipeline scheduling period in microseconds. + * \return Created comp_dev pointer, or NULL on failure. + */ +struct comp_dev *sof_static_module_create_default(const struct comp_driver *drv, + struct comp_ipc_config *cfg, + const struct sof_static_comp *cdesc, + uint32_t period_us); + +/** + * \brief Set kcontrol value by control ID. + * + * Dispatches to the target module via its registered static operations + * (struct sof_static_module_ops), or invokes the topology's custom_control_handler. + * + * \param[in] ctrl_id Unique control ID. + * \param[in] val Value to apply. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set(uint32_t ctrl_id, int32_t val); + +/** + * \brief Set kcontrol value for a specific audio channel. + * + * Dispatches control value to a specific audio channel of the target component + * via apply_volume_chan, apply_switch_chan, or apply_enum. + * + * \param[in] ctrl_id Unique control ID. + * \param[in] channel Channel index (0-based). + * \param[in] val Value to apply. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set_chan(uint32_t ctrl_id, uint32_t channel, int32_t val); + +/** + * \brief Set binary raw configuration blob on a static kcontrol by control ID. + * + * Dispatches the raw binary configuration blob to the target component associated + * with the designated kcontrol via apply_binary or module adapter large config. + * + * \param[in] ctrl_id Target kcontrol identifier. + * \param[in] data Pointer to raw configuration blob. + * \param[in] size Size of data blob in bytes. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set_binary(uint32_t ctrl_id, const void *data, size_t size); + +/** + * \brief Retrieve cached kcontrol value by control ID. + * \param[in] ctrl_id Unique control ID. + * \param[out] val Pointer to store retrieved control value. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_get(uint32_t ctrl_id, int32_t *val); + +/** + * \brief Find kcontrol ID by human-readable name. + * \param[in] name Control name string to search for. + * \return Control ID >= 0 if found, -ENOENT if not found. + */ +int sof_static_kcontrol_find_by_name(const char *name); + +/** + * \brief Dispatch UAC2 feature unit control to registered kcontrols. + * \param[in] entity_id UAC2 Feature Unit Entity ID. + * \param[in] channel Audio channel index (0 = master/all). + * \param[in] val Value from USB request (e.g. 8.8 fixed-point dB volume or boolean mute). + * \param[in] is_volume True for volume command, false for mute command. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_set_by_uac2(uint8_t entity_id, uint8_t channel, int32_t val, bool is_volume); + +/** + * \brief Retrieve kcontrol value formatted for UAC2 feature unit response. + * \param[in] entity_id UAC2 Feature Unit Entity ID. + * \param[in] channel Audio channel index. + * \param[out] val Pointer to store retrieved value. + * \param[in] is_volume True for volume query, false for mute query. + * \return 0 on success, negative errno on failure. + */ +int sof_static_kcontrol_get_by_uac2(uint8_t entity_id, uint8_t channel, int32_t *val, bool is_volume); + +/** + * \brief Trigger pipeline start or stop associated with a UAC2 terminal ID. + * \param[in] terminal_id Bound UAC2 Terminal Entity ID. + * \param[in] start True to start pipeline, false to pause/stop. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_trigger_by_uac2_term(uint8_t terminal_id, bool start); + +/** + * \brief Start a static pipeline by pipeline ID. + * + * Prepares the pipeline and constituent components if needed, propagates + * start triggers, transitions state to COMP_STATE_ACTIVE, and activates + * scheduling copy tasks. + * + * \param[in] pipeline_id Target pipeline ID. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_start(uint32_t pipeline_id); + +/** + * \brief Stop a static pipeline by pipeline ID. + * + * Propagates stop triggers, cancels scheduling copy tasks, and transitions + * pipeline state to COMP_STATE_PAUSED. + * + * \param[in] pipeline_id Target pipeline ID. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_stop(uint32_t pipeline_id); + +/** + * \brief Directly trigger a static pipeline start or stop by pipeline ID. + * + * Synchronously transitions the pipeline state machine, propagates triggers + * to all constituent components, and manages scheduling task execution. + * Dispatches directly to sof_static_pipeline_start() or sof_static_pipeline_stop(). + * + * \param[in] pipeline_id Target pipeline ID. + * \param[in] start True to start pipeline, false to stop. + * \return 0 on success, negative errno on failure. + */ +int sof_static_pipeline_trigger(uint32_t pipeline_id, bool start); + +/** + * \brief Re-negotiate the stream sample rate for a specific static pipeline. + * + * Re-negotiates the sample rate for the designated pipeline and its constituent + * components and buffers while the pipeline is in paused or stopped state. + * Validates that all constituent components support the requested rate according + * to their capability masks, reconfigures buffer period sizes, and propagates + * pipeline_params() and pipeline_prepare() through the pipeline graph. + * + * \param[in] pipeline_id Target pipeline identifier. + * \param[in] rate Target sample rate in Hz (e.g. 16000, 44100, 48000, 96000). + * \return 0 on success, -EBUSY if the pipeline is currently active, + * -EINVAL if the rate is unsupported by component capabilities, + * or negative errno on failure. + */ +int sof_static_pipeline_set_sample_rate(uint32_t pipeline_id, uint32_t rate); + +/** + * \brief Retrieve the current stream sample rate for a specific static pipeline. + * + * \param[in] pipeline_id Target pipeline identifier. + * \return Pipeline sample rate in Hz, or 0 if pipeline not found. + */ +uint32_t sof_static_pipeline_get_sample_rate(uint32_t pipeline_id); + +/** + * \brief Set global sample rate across active static pipelines. + * + * In pipelines with sample rate converters (SRC / ASRC), individual pipeline + * sections maintain their distinct sample rates according to their component + * capabilities and buffer configurations. + * + * \param[in] rate Target primary sample rate in Hz (e.g. 48000). + * \return 0 on success, negative errno on failure. + */ +int sof_static_set_sample_rate(uint32_t rate); + +/** + * \brief Retrieve current primary sample rate. + * \return Current sample rate in Hz. + */ +uint32_t sof_static_get_sample_rate(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __SOF_AUDIO_PIPELINE_STATIC_PIPELINE_H__ */ + +/** @} */