diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 8accb25738a2..777341fe8a84 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -51,6 +51,16 @@ 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" + 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 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..78e6041b0d94 100644 --- a/src/audio/asrc/asrc.c +++ b/src/audio/asrc/asrc.c @@ -846,4 +846,39 @@ 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); + + 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..b20c1d9f25e5 100644 --- a/src/audio/drc/drc.c +++ b/src/audio/drc/drc.c @@ -481,4 +481,30 @@ 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) + return -EINVAL; + + struct drc_comp_data *cd = module_get_private_data(mod); + + if (cd) + cd->enable_switch = (val != 0); + + 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..90547b4bec78 100644 --- a/src/audio/eq_iir/eq_iir.c +++ b/src/audio/eq_iir/eq_iir.c @@ -258,4 +258,43 @@ 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) + return -EINVAL; + + struct comp_data *cd = module_get_private_data(mod); + + if (cd) { + if (val == 0) { + cd->eq_iir_func = eq_iir_pass; + } else if (cd->iir_delay_size) { +#if CONFIG_FORMAT_FLOAT + struct comp_buffer *sourceb = comp_dev_get_first_data_producer(dev); + + if (sourceb && audio_stream_get_frm_fmt(&sourceb->stream) == SOF_IPC_FRAME_FLOAT) + cd->eq_iir_func = eq_iir_float_default; + else + cd->eq_iir_func = eq_iir_s16_default; +#else + cd->eq_iir_func = eq_iir_s16_default; +#endif + } + } + 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..51177298983b 100644 --- a/src/audio/level_multiplier/level_multiplier.c +++ b/src/audio/level_multiplier/level_multiplier.c @@ -204,4 +204,46 @@ 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) + return -EINVAL; + + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + + if (cd) + cd->gain = val; + + 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) + return -EINVAL; + + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + + if (cd) + 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..35e4966d0f0e 100644 --- a/src/audio/pipeline/README.md +++ b/src/audio/pipeline/README.md @@ -240,6 +240,246 @@ 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 +static const struct sof_static_kcontrol s_controls[] = { + SOF_STATIC_KCONTROL_VOLUME( + .id = 1, + .name = "Master Playback Volume", + .target_comp_id = 3, /* Targets VOL_PB (3) */ + .min = 0, + .max = 65536, + .def = 65536, /* 0 dB default */ + .channels = 2, + .uac2_entity_id = 10, /* Bound to UAC2 Feature Unit 10 */ + ), + SOF_STATIC_KCONTROL_SWITCH( + .id = 2, + .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 = 3, + .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 == 3) { + /* 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..9e111a096075 --- /dev/null +++ b/src/audio/pipeline/static_pipeline_loader.c @@ -0,0 +1,1282 @@ +// 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 "static_pipeline_modules.h" + +LOG_MODULE_REGISTER(static_pipeline_loader, CONFIG_SOF_LOG_LEVEL); + +/* + * Maximum capacity limits for static topology tracking tables. + * Sized generously to support complex embedded multi-channel topologies + * while avoiding dynamic heap overhead during runtime lookups. + */ +#define MAX_STATIC_PIPELINES 16 +#define MAX_STATIC_COMPS 64 +#define MAX_STATIC_BUFFERS 64 +#define MAX_STATIC_CONTROLS 64 + +/* Active declarative topology descriptor */ +static const struct sof_static_topology *s_active_topo; + +/* Static tracking tables for fast ID-to-pointer lookups */ +static struct pipeline *s_pipelines[MAX_STATIC_PIPELINES]; +static uint32_t s_pipeline_ids[MAX_STATIC_PIPELINES]; +static size_t s_num_pipelines; + +static struct comp_dev *s_comps[MAX_STATIC_COMPS]; +static uint32_t s_comp_ids[MAX_STATIC_COMPS]; +static size_t s_num_comps; + +static struct comp_buffer *s_buffers[MAX_STATIC_BUFFERS]; +static uint32_t s_buffer_ids[MAX_STATIC_BUFFERS]; +static size_t s_num_buffers; + +/* Cached kcontrol values */ +static int32_t s_control_vals[MAX_STATIC_CONTROLS]; + +/* + * Primary stream sample rate in Hz. Initialized to 0 (unassigned) and + * dynamically resolved from the declarative topology descriptor at boot + * (or updated at runtime via sof_static_set_sample_rate()). + */ +static uint32_t s_sample_rate; + +/** + * \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 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 configuration blob payload */ + if (!dev || !abi_blob || abi_blob_total_size == 0) + return -EINVAL; + + const struct sof_abi_hdr *hdr = (const struct sof_abi_hdr *)abi_blob; + 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; + } + +#if CONFIG_IPC_MAJOR_4 + /* + * In IPC4 mode, pass the raw configuration payload directly following the + * ABI header to the module adapter's set_configuration callback. + */ + 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); +#else + /* + * In IPC3 mode, wrap the configuration payload in a struct sof_ipc_ctrl_data + * with command SOF_CTRL_CMD_BINARY as expected by IPC3 processing components. + */ + size_t cdata_size = sizeof(struct sof_ipc_ctrl_data) + sizeof(struct sof_abi_hdr) + hdr->size; + struct sof_ipc_ctrl_data *cdata = rzalloc(SOF_MEM_FLAG_USER, cdata_size); + if (!cdata) { + LOG_ERR("Failed to allocate ctrl_data size %zu", cdata_size); + return -ENOMEM; + } + + cdata->cmd = SOF_CTRL_CMD_BINARY; + cdata->num_elems = hdr->size; + cdata->data[0].magic = hdr->magic; + cdata->data[0].type = hdr->type; + cdata->data[0].size = hdr->size; + cdata->data[0].abi = hdr->abi; + memcpy_s(cdata->data[0].data, hdr->size, (const uint8_t *)abi_blob + sizeof(struct sof_abi_hdr), hdr->size); + + int ret = mod->dev->drv->adapter_ops->set_configuration(mod, 0, MODULE_CFG_FRAGMENT_SINGLE, + hdr->size, (const uint8_t *)cdata, + hdr->size, NULL, 0); + rfree(cdata); +#endif + + /* 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 = get_sample_bitdepth(fmt) / 8; + + /* 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 Resolve the primary stream sample rate from declarative topology. + * + * Scans pipelines and components for declared sample rates, falling back to + * standard 48 kHz if completely unspecified. + * + * \param[in] topo Topology descriptor pointer. + */ +static void sof_static_topology_resolve_rate(const struct sof_static_topology *topo) +{ + if (s_sample_rate) + return; + + /* First check if the primary pipeline declares a default rate */ + for (size_t p = 0; p < topo->num_pipelines; p++) { + if (topo->pipelines[p].default_rate) { + s_sample_rate = topo->pipelines[p].default_rate; + break; + } + } + + /* Fallback to checking the first component with a valid default rate */ + if (!s_sample_rate) { + for (size_t c = 0; c < topo->num_comps; c++) { + if (topo->comps[c].caps.default_rate) { + s_sample_rate = topo->comps[c].caps.default_rate; + break; + } + } + } + + /* If completely unspecified in topology, log and fall back to standard 48 kHz */ + if (!s_sample_rate) { + LOG_INF("Primary sample rate unspecified in topology, falling back to 48000 Hz"); + s_sample_rate = 48000; + } +} + +/** + * \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. + * + * \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->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 ? pdesc->time_domain : SOF_TIME_DOMAIN_TIMER; + + /* Record pipeline instance in static tracking array */ + s_pipelines[s_num_pipelines] = pipe; + s_pipeline_ids[s_num_pipelines] = pdesc->pipeline_id; + s_num_pipelines++; + + LOG_INF("Pipeline %u ('%s') initialized (period %u us, %u frames)", + pdesc->pipeline_id, pdesc->name ? pdesc->name : "", pipe->period, pipe->frames_per_sched); + } + + 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) +{ + /* Derive DAI sampling frequency from component caps or resolved rate */ + uint32_t dai_rate = cdesc->caps.default_rate ? cdesc->caps.default_rate : s_sample_rate; + 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) + return NULL; + + struct sof_ipc_dai_config spec_cfg = { + .type = cdesc->ep.dai.dai_type, + .dai_index = cdesc->ep.dai.dai_index, + .format = cdesc->ep.dai.format, + }; + + /* Invoke optional platform/endpoint-specific configuration callback */ + if (cdesc->ep.dai.configure) { + int ret_cfg = cdesc->ep.dai.configure(dev, &dai_cfg, &spec_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 */ + struct dai_data *dd = comp_get_drvdata(dev); + + if (dd) { + comp_dai_config(dd, dev, &dai_cfg, &spec_cfg); + if (!dd->dai_spec_config) { + dd->dai_spec_config = sof_heap_alloc(dd->alloc_ctx.heap, + SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, + sizeof(struct sof_ipc_dai_config), 0); + if (dd->dai_spec_config) + memcpy(dd->dai_spec_config, &spec_cfg, sizeof(struct sof_ipc_dai_config)); + } + } + + /* Configure underlying physical DAI controller instance */ + struct dai *dai = dai_get(cdesc->ep.dai.dai_type, cdesc->ep.dai.dai_index, DAI_CREAT); + + if (dai) { + dai_set_config(dai, &dai_cfg, &spec_cfg, sizeof(spec_cfg)); + dai_put(dai); + } + + 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, + const 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; + } + + /* Populate standard SOF component IPC configuration */ + struct comp_ipc_config cfg = { + .id = cdesc->id, + .pipeline_id = cdesc->pipeline_id, + .core = 0, + .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); + 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); + + /* Find owning pipeline descriptor to resolve scheduling period */ + 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; + } + } + 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 : + s_sample_rate); + 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) + send_comp_config(dev, cdesc->init_blob, cdesc->init_blob_size); + + /* 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) + 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; + } + + /* Default fallbacks if unassigned */ + if (!rate) + rate = s_sample_rate; + if (!channels) + channels = 2; + if (!fmt) + 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 */ + pipeline_connect(src, buf, PPL_CONN_DIR_COMP_TO_BUFFER); + pipeline_connect(sink, buf, PPL_CONN_DIR_BUFFER_TO_COMP); + 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) + continue; + + /* 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); + + /* 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 */ + uint32_t rate = pdesc->default_rate; + if (!rate && cdesc) + rate = cdesc->caps.default_rate; + if (!rate) + rate = s_sample_rate; + + /* 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 = get_sample_bitdepth(fmt) / 8; + 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); + int ret_prep = pipeline_prepare(pipe, host_or_sched); + LOG_INF("Pipeline %u params (rate %u, ch %u, fmt %u) ret=%d, prepare ret=%d", + pdesc->pipeline_id, rate, channels, fmt, ret_prms, ret_prep); + } + } + + 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 = 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; + } + } + + /* Determine component-specific sample rate */ + uint32_t rate = cdesc->caps.default_rate; + if (!rate && pdesc) + rate = pdesc->default_rate; + if (!rate) + rate = s_sample_rate; + + /* 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 = get_sample_bitdepth(fmt) / 8; + 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); + int ret_prep = comp_prepare(dev); + LOG_INF("Component %u ('%s') explicit prepare (rate %u, ch %u, fmt %u): prms=%d prep=%d state=%d", + dev_comp_id(dev), cdesc->name, rate, channels, fmt, ret_prms, ret_prep, 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. + */ +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; + sof_static_kcontrol_set(ctl->id, ctl->def); + 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 0: Resolve primary stream sample rate + * 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. + */ +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; + } + + LOG_INF("=== Loading Static Audio Topology: '%s' ===", topo->name ? topo->name : "Unnamed"); + s_active_topo = topo; + + /* Stage 0: Dynamically resolve stream sample rate */ + sof_static_topology_resolve_rate(topo); + + /* Stage 1: Allocate top-level pipelines */ + ret = sof_static_topology_init_pipelines(topo); + if (ret < 0) + return ret; + + /* Stage 2: Instantiate components */ + ret = sof_static_topology_init_comps(topo); + if (ret < 0) + return ret; + + /* Stage 3: Allocate intermediate audio buffers */ + ret = sof_static_topology_init_buffers(topo); + if (ret < 0) + return ret; + + /* Stage 4: Connect pipeline graph routes */ + ret = sof_static_topology_init_routes(topo); + if (ret < 0) + return ret; + + /* Stage 5: Bind pipeline endpoints and propagate stream parameters */ + ret = sof_static_topology_init_pipeline_endpoints(topo); + if (ret < 0) + return ret; + + /* Stage 6: Explicitly configure and prepare all components */ + ret = sof_static_topology_prepare_comps(topo); + if (ret < 0) + return ret; + + /* Stage 7: Initialize kcontrols with default values */ + ret = sof_static_topology_init_controls(topo); + if (ret < 0) + return ret; + + 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; +} + +/** + * \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; + + /* 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; + + /* Dispatch control value to target component using registered operations */ + if (ops) { + switch (ctl->type) { + case SOF_STATIC_CTRL_VOLUME: + /* Linear volume fader control */ + if (ops->apply_volume) + ops->apply_volume(dev, ctl->channels, val); + break; + + case SOF_STATIC_CTRL_SWITCH: + /* Boolean mute or module bypass switch */ + if (ops->apply_switch) + ops->apply_switch(dev, ctl->channels, val); + break; + + case SOF_STATIC_CTRL_ENUM: + /* Enumerated route or channel selector */ + if (ops->apply_enum) + 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); + break; + } + } + + /* 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 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 */ + 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; + } + + /* Propagate START trigger across the pipeline graph */ + ret = pipeline_trigger_run(pipe, dev, COMP_TRIGGER_START); + if (ret < 0) { + LOG_ERR("Pipeline %u start trigger failed: %d", pipeline_id, ret); + return ret; + } + + /* Explicitly notify all constituent components and mark state ACTIVE */ + if (s_active_topo) { + for (size_t j = 0; j < s_active_topo->num_comps; j++) { + if (s_active_topo->comps[j].pipeline_id == pipeline_id) { + struct comp_dev *c = sof_static_comp_get(s_active_topo->comps[j].id); + if (c) { + comp_trigger(c, COMP_TRIGGER_PRE_START); + comp_trigger(c, COMP_TRIGGER_START); + c->state = COMP_STATE_ACTIVE; + } + } + } + } + + 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; + } + + /* Explicitly notify all constituent components and mark state PAUSED */ + if (s_active_topo) { + for (size_t j = 0; j < s_active_topo->num_comps; j++) { + if (s_active_topo->comps[j].pipeline_id == pipeline_id) { + struct comp_dev *c = sof_static_comp_get(s_active_topo->comps[j].id); + if (c) { + comp_trigger(c, COMP_TRIGGER_STOP); + c->state = COMP_STATE_PAUSED; + } + } + } + } + + 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 Override the primary stream sample rate across active static pipelines. + * \param[in] rate Target sample rate in Hz (e.g. 48000). + * \return 0 on success. + */ +int sof_static_set_sample_rate(uint32_t rate) +{ + s_sample_rate = rate; + return 0; +} + +/** + * \brief Retrieve the current primary stream sample rate. + * \return Current sample rate in Hz. + */ +uint32_t sof_static_get_sample_rate(void) +{ + /* + * Return the active primary sample rate. If topology init has run, + * this is derived from the declarative topology or runtime override; + * if queried before init, fall back to 48 kHz. + */ + return s_sample_rate ? s_sample_rate : 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..ad723c29592f --- /dev/null +++ b/src/audio/pipeline/static_pipeline_modules.c @@ -0,0 +1,240 @@ +// 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 "static_pipeline_modules.h" + +LOG_MODULE_REGISTER(static_pipeline_modules, CONFIG_SOF_LOG_LEVEL); + +/* ========================================================================= + * Static Module Operations Registry + * ========================================================================= */ + +static 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 + * ========================================================================= */ + +#if CONFIG_IPC4_BASE_CONFIG || CONFIG_IPC_MAJOR_4 + +/* 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; + 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); + 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; + + 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 + base_cfg->audio_fmt.s_type = IPC4_TYPE_SIGNED_INTEGER; + + 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 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 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: + 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; + } +} +#endif + +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; + } + +#if CONFIG_IPC4_BASE_CONFIG || CONFIG_IPC_MAJOR_4 + 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); +#else + static const uint8_t dummy_buf[16] = {0}; + struct ipc_config_process empty_proc_spec = { + .size = 0, + .data = dummy_buf, + }; + return drv->ops.create(drv, cfg, &empty_proc_spec); +#endif +} 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..193e1f29e862 --- /dev/null +++ b/src/audio/pipeline/static_pipeline_uac2.c @@ -0,0 +1,189 @@ +// 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 + +LOG_MODULE_REGISTER(static_pipeline_uac2, CONFIG_SOF_LOG_LEVEL); + +/* Standard 0 dB gain constant for SOF volume representation (Q1.16 format) */ +#define SOF_VOL_ZERO_DB 65536 + +/** + * \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 16-bit linear volume using + * an integer 6 dB per bit shift approximation: + * - >= 0 dB maps to SOF_VOL_ZERO_DB (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 16-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; + + /* Linear interpolation between 6 dB bit shifts */ + int rem = db_x10 % 60; + uint64_t v = (uint64_t)SOF_VOL_ZERO_DB >> shift; + v = (v * (60 - rem)) / 60; + return (int32_t)v; +} + +/** + * \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. + * + * \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 (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) { + if (is_volume && ctl->type == SOF_STATIC_CTRL_VOLUME) { + /* Translate UAC2 8.8 dB volume to linear SOF volume */ + int32_t sof_vol = uac2_to_sof_volume((int16_t)val); + return sof_static_kcontrol_set(ctl->id, sof_vol); + } else if (!is_volume && ctl->type == SOF_STATIC_CTRL_SWITCH) { + /* In UAC2: val=1 means MUTED, so enabled switch state is 0 */ + return sof_static_kcontrol_set(ctl->id, val ? 0 : 1); + } + } + } + + 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. + * \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) +{ + 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) { + int32_t ctl_val = 0; + int ret = sof_static_kcontrol_get(ctl->id, &ctl_val); + if (ret < 0) { + LOG_ERR("sof_static_kcontrol_get_by_uac2: failed to get ctl %u", ctl->id); + return ret; + } + + if (is_volume && ctl->type == SOF_STATIC_CTRL_VOLUME) { + *val = ctl_val; + return 0; + } else if (!is_volume && ctl->type == SOF_STATIC_CTRL_SWITCH) { + /* In UAC2: mute is 1 if disabled (0), 0 if enabled (1) */ + *val = (ctl_val == 0) ? 1 : 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..89e74a2d0531 100644 --- a/src/audio/selector/selector.c +++ b/src/audio/selector/selector.c @@ -1182,3 +1182,73 @@ 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) +{ + 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); +} + +static int sel_static_apply_enum(struct comp_dev *dev, uint32_t channel, int32_t val) +{ +#if CONFIG_IPC_MAJOR_3 + uint8_t cbuf[sizeof(struct sof_ipc_ctrl_data) + sizeof(struct sof_ipc_ctrl_value_chan)] = {0}; + struct sof_ipc_ctrl_data *cdata = (struct sof_ipc_ctrl_data *)cbuf; + + cdata->cmd = SOF_CTRL_CMD_ENUM; + cdata->type = SOF_CTRL_TYPE_VALUE_CHAN_SET; + cdata->num_elems = 1; + cdata->chanv[0].channel = channel; + cdata->chanv[0].value = val; + if (dev->drv && dev->drv->ops.cmd) + return dev->drv->ops.cmd(dev, COMP_CMD_SET_VALUE, cdata, sizeof(cbuf)); + return 0; +#else + struct processing_module *mod = comp_mod(dev); + + if (!mod) + return -EINVAL; + + struct comp_data *cd = module_get_private_data(mod); + + if (cd) + cd->config.sel_channel = val; + + return 0; +#endif +} + +#if CONFIG_IPC_MAJOR_3 +static struct sof_static_module_ops sel_static_ops = { + .uuid = &selector_uuid, + .create = sel_static_create, + .apply_enum = sel_static_apply_enum, +}; +DECLARE_STATIC_MODULE_OPS(selector, &sel_static_ops); +#endif + +#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..c119a0987f09 100644 --- a/src/audio/tdfb/tdfb.c +++ b/src/audio/tdfb/tdfb.c @@ -945,4 +945,30 @@ 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) + return -EINVAL; + + struct tdfb_comp_data *cd = module_get_private_data(mod); + + if (cd) + cd->beam_on = (val != 0); + + 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..1c903f5026cb 100644 --- a/src/audio/volume/volume.c +++ b/src/audio/volume/volume.c @@ -889,4 +889,103 @@ 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) +{ +#if CONFIG_IPC_MAJOR_4 + 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); +#else + struct ipc_config_volume vol_cfg = { + .channels = cdesc->caps.max_channels ? cdesc->caps.max_channels : 2, + .min_value = 0, + .max_value = INT32_MAX, + .ramp = SOF_VOLUME_LINEAR, + .initial_ramp = 0, + }; + struct ipc_config_process spec = { + .size = sizeof(vol_cfg), + .data = (const uint8_t *)&vol_cfg, + }; + return drv->ops.create(drv, cfg, &spec); +#endif +} + +static int vol_static_apply_volume(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) + return -EINVAL; + + for (uint32_t ch = 0; ch < channels; ch++) + volume_set_chan(mod, ch, val, true); + + return 0; +} + +static int vol_static_apply_switch(struct comp_dev *dev, uint32_t channels, int32_t val) +{ + struct processing_module *mod = comp_mod(dev); + + if (!mod) + return -EINVAL; + + 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 struct sof_static_module_ops vol_static_ops = { + .uuid = &volume_uuid, + .create = vol_static_create, + .apply_volume = vol_static_apply_volume, + .apply_switch = vol_static_apply_switch, +}; + +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_switch = vol_static_apply_switch, +}; + +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..15bee800ba53 --- /dev/null +++ b/src/include/sof/audio/pipeline/static_pipeline.h @@ -0,0 +1,807 @@ +/* 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, + struct sof_ipc_dai_config *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 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 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); + + /** 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) \ + __attribute__((constructor)) __used \ + static 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 */ + 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. + */ +#define SOF_STATIC_BUFFER(...) \ + { .flags = SOF_MEM_FLAG_DMA | SOF_MEM_FLAG_USER, __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 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 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. + */ +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__ */ + +/** @} */