Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
326 changes: 326 additions & 0 deletions tools/topology/topology2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
# SOF Topology2

Topology2 is the second-generation ALSA topology definition system for SOF (Sound Open
Firmware). It defines audio processing pipelines, PCM streams, DAI configurations, and
routing graphs using `.conf` files that are compiled into binary `.tplg` files consumed by
the SOF firmware at runtime.

The build pipeline works as follows: `.conf` source files are processed by `alsatplg`
(the ALSA Topology Configuration compiler) to produce `.tplg` binary files. The cmake
build system orchestrates this compilation, with each topology target specified as a tuple
of input configuration, output name, and variable overrides.

Topology2 uses a class-based object model built on four core concepts:

* **Classes** (`Class.Pipeline`, `Class.Widget`, `Class.PCM`) define reusable templates
with default attribute values
* **Objects** (`Object.Pipeline`, `Object.Widget`, `Object.PCM`) instantiate classes with
specific parameter values
* **Define blocks** provide variable substitution using `$VARIABLE` syntax, enabling
parameterized topologies
* **IncludeByKey** enables conditional includes based on variable values, used primarily
for platform-specific overrides

Building topologies requires `alsatplg` version 1.2.7 or later. The version check is
enforced in `CMakeLists.txt` at configure time.

## Directory Structure

```text
tools/topology/topology2/
├── CMakeLists.txt # Build system entry point
├── get_abi.sh # ABI version extraction script
├── cavs-sdw.conf # SoundWire topology entry point
├── sof-hda-generic.conf # HDA generic topology entry point
├── cavs-mixin-mixout-hda.conf # HDA with mixer pipelines
├── cavs-nocodec.conf # SSP nocodec topology
├── ... # Other top-level .conf entry points
├── include/
│ ├── common/ # Core class definitions (PCM, route, audio formats)
│ ├── components/ # Widget/component classes (gain, mixin, EQ, DRC)
│ ├── controls/ # Control classes (mixer, enum, bytes)
│ ├── dais/ # DAI classes (SSP, DMIC, HDA, ALH)
│ └── pipelines/ # Pipeline template classes
│ └── cavs/ # CAVS-architecture pipeline classes
├── platform/
│ └── intel/ # Platform-specific overrides (tgl, mtl, lnl, ptl)
├── production/ # CMake targets for production topologies
│ ├── tplg-targets-ace1.cmake # ACE1 (MTL) targets
│ ├── tplg-targets-ace2.cmake # ACE2 (LNL) targets
│ ├── tplg-targets-ace3.cmake # ACE3 (PTL) targets
│ └── ... # Additional platform target files
├── development/ # CMake targets for development/testing
└── doc/ # Doxygen documentation source
```

## Best Practices for Adding New Topology Definitions

### Topology Structure

A top-level topology `.conf` file follows a layered configuration pattern:

```conf
# 1. Search directories
<searchdir:include>
<searchdir:include/common>
<searchdir:include/components>
<searchdir:include/dais>
<searchdir:include/pipelines/cavs>
<searchdir:platform/intel>

# 2. Include class files
<vendor-token.conf>
<tokens.conf>
<pcm.conf>
<host-copier-gain-mixin-playback.conf>
<mixout-gain-alh-dai-copier-playback.conf>

# 3. Define block (default variable values)
Define {
PLATFORM ""
NUM_HDMIS 3
DEEP_BUFFER_PCM_ID 31
}

# 4. Platform overrides (conditional includes)
IncludeByKey.PLATFORM {
"mtl" "platform/intel/mtl.conf"
"lnl" "platform/intel/lnl.conf"
"ptl" "platform/intel/ptl.conf"
}

# 5. Conditional feature includes
IncludeByKey.NUM_HDMIS {
"3" "platform/intel/hdmi-generic.conf"
}

# 6. DAI, Pipeline, PCM objects
# 7. Route definitions
```

### Reusing Existing Bases

The most common way to add a new topology is to reuse an existing base `.conf` file and
override variables through a cmake target entry. Targets are defined in
`production/tplg-targets-*.cmake` files using a tuple format:

```text
"input-conf;output-name;variables"
```

For example, to add a new SoundWire topology variant for ACE2 (Lunar Lake):

```text
"cavs-sdw\;sof-lnl-sdw-cs42l43-l0-cs35l56-l12\;PLATFORM=lnl,NUM_SDW_AMP_LINKS=2"
```

The first element is the base `.conf` file (without extension), the second is the output
`.tplg` filename, and the third is a comma-separated list of variable overrides.

### Creating a New Base Topology

When existing bases do not cover a new use case, create a new top-level `.conf` file:

1. Create a new `.conf` file in `tools/topology/topology2/` following the layered
structure described above
2. Include the required class files from `include/` directories via search directives
3. Define default variables in a `Define` block
4. Add `IncludeByKey.PLATFORM` entries for platform-specific overrides
5. Instantiate DAI, Pipeline, and PCM objects with appropriate IDs
6. Define routes connecting FE mixin outputs to BE mixout inputs
7. Register the topology as a cmake target in the appropriate
`production/tplg-targets-*.cmake` file

### PCM ID Conventions

PCM IDs identify audio streams exposed to userspace via ALSA. Each PCM ID must be unique
within a single topology. Different topology families (SoundWire vs HDA) use different
default ID ranges for the same endpoint types.

**SoundWire PCM IDs:**

| Endpoint | Default PCM ID | Override Variable |
|---|---|---|
| Jack (playback/capture) | 0 | — |
| Speaker amplifier | 2 | — |
| SDW DMIC | 4 | — |
| HDMI 1 | 5 | `HDMI1_PCM_ID` |
| HDMI 2 | 6 | `HDMI2_PCM_ID` |
| HDMI 3 | 7 | `HDMI3_PCM_ID` |
| PCH DMIC0 | 10 | `DMIC0_PCM_ID` |
| PCH DMIC1 | 11 | `DMIC1_PCM_ID` |
| Jack Echo Ref | 11 | `SDW_JACK_ECHO_REF_PCM_ID` |
| Speaker Echo Ref | 12 | `SDW_SPK_ECHO_REF_PCM_ID` |
| Bluetooth | 2 or 20 | `BT_PCM_ID` |
| Deep Buffer (Jack) | 31 | `DEEP_BUFFER_PCM_ID` |
| Deep Buffer (Speaker) | 35 | `DEEP_BUFFER_PCM_ID_2` |
| DMIC Deep Buffer | 46 | `DMIC0_DEEP_BUFFER_PCM_ID` |
| Compressed Playback 1 | 50 | `COMPR_PCM_ID` |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compress Jack Out

| Compressed Playback 2 | 52 | `COMPR_2_PCM_ID` |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compress Speaker


> **Note:** Bluetooth defaults to PCM ID 2 in some topologies and 20 in others. Use the
> `BT_PCM_ID` override variable to set the correct value when BT coexists with a speaker
> amplifier (which also uses PCM ID 2 by default).

**HDA PCM IDs:**

| Endpoint | Default PCM ID | Override Variable |
|---|---|---|
| HDA Analog | 0 | — |
| HDMI 1 | 3 | `HDMI1_PCM_ID` |
| HDMI 2 | 4 | `HDMI2_PCM_ID` |
| HDMI 3 | 5 | `HDMI3_PCM_ID` |
| DMIC0 | 6 | `DMIC0_PCM_ID` |
| Deep Buffer | 31 | `DEEP_BUFFER_PCM_ID` |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

| Compress HDA Analog | 50 | COMPR_PCM_ID |


Key rules:

* PCM ID 0 is always the primary playback endpoint
* PCM IDs must be unique within a single topology
* When features coexist (SDW + PCH DMIC + HDMI), adjust IDs via `Define` overrides in
cmake targets to avoid conflicts
* Different topology families (SDW vs HDA) use different default ID ranges for the same
endpoint types

### Pipeline ID Conventions

Pipeline IDs are set via the `index` attribute on pipeline objects. Front-end (FE) and
back-end (BE) pipelines are paired, with the FE pipeline at index N and the BE pipeline
at index N+1.

In SoundWire topologies, pipeline indexes follow the convention documented in
`sdw-amp-generic.conf` and `sdw-dmic-generic.conf`: pipeline index = PCM ID × 10. HDMI
pipelines use a stride-10 pattern where the host pipeline is at N0 and the DAI pipeline
is at N1 (50/51, 60/61, 70/71, 80/81).

**SoundWire Pipeline IDs:**

| Pipeline | Default Index | Override Variable |
|---|---|---|
| Jack Playback FE / BE | 0 / 1 | — |
| Jack Capture FE / BE | 10 / 11 | — |
| Deep Buffer (Jack) | 15 | `DEEP_BUFFER_PIPELINE_ID` |
| Deep Buffer (Speaker) | 16 | `DEEP_BUFFER_PIPELINE_ID_2` |
| Speaker FE / BE | 20 / 21 | — |
| SDW DMIC FE / BE | 40 / 41 | `SDW_DMIC_HOST_PIPELINE_ID` |
| HDMI 1 Host / DAI | 50 / 51 | `HDMI1_HOST_PIPELINE_ID` / `HDMI1_DAI_PIPELINE_ID` |
| HDMI 2 Host / DAI | 60 / 61 | `HDMI2_HOST_PIPELINE_ID` / `HDMI2_DAI_PIPELINE_ID` |
| HDMI 3 Host / DAI | 70 / 71 | `HDMI3_HOST_PIPELINE_ID` / `HDMI3_DAI_PIPELINE_ID` |
| HDMI 4 Host / DAI | 80 / 81 | `HDMI4_HOST_PIPELINE_ID` / `HDMI4_DAI_PIPELINE_ID` |
| Compressed 1 / 2 | 90 / 92 | `COMPR_PIPELINE_ID` / `COMPR_2_PIPELINE_ID` |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compress Jack and Speaker...

| PCH DMIC0 Host / DAI | 100 / 101 | `DMIC0_HOST_PIPELINE_ID` / `DMIC0_DAI_PIPELINE_ID` |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why only these random pipeline IDs? There is pipeline with ID 22 for example and based on this readme, it is up for grab, when if you define a pipeline with 22, it will have really fun side effects as it is used by something else...


**HDA Pipeline IDs:**

| Pipeline | Default Index | Override Variable |
|---|---|---|
| Analog Playback FE / BE | 1 / 2 | — |
| Analog Capture FE / BE | 3 / 4 | — |
| DMIC0 Host / DAI | 11 / 12 | `DMIC0_HOST_PIPELINE_ID` / `DMIC0_DAI_PIPELINE_ID` |
| Deep Buffer | 15 | `DEEP_BUFFER_PIPELINE_ID` |
| HDMI 1 Host / DAI | 50 / 51 | `HDMI1_HOST_PIPELINE_ID` / `HDMI1_DAI_PIPELINE_ID` |
| HDMI 2 Host / DAI | 60 / 61 | `HDMI2_HOST_PIPELINE_ID` / `HDMI2_DAI_PIPELINE_ID` |
| HDMI 3 Host / DAI | 70 / 71 | `HDMI3_HOST_PIPELINE_ID` / `HDMI3_DAI_PIPELINE_ID` |
| HDMI 4 Host / DAI | 80 / 81 | `HDMI4_HOST_PIPELINE_ID` / `HDMI4_DAI_PIPELINE_ID` |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compress HDA Analog is 90 and who knows what pipeline IDs are in use in HDA topology, I would not dare to guess...


Key rules:

* FE and BE pipelines are paired: FE = N, BE = N+1
* SDW convention: pipeline index = PCM ID × 10 (documented in `sdw-amp-generic.conf` and
`sdw-dmic-generic.conf`)
* HDMI uses stride-10: Host = N0, DAI = N1
* Pipeline IDs must be unique within a single topology
* When adding new endpoints, select IDs in unused ranges that do not conflict with
existing assignments

### Widget Naming

Widget names follow the convention `<type>.<pipeline-index>.<instance>`. Examples:

* `gain.1.1` — gain widget in pipeline 1, instance 1
* `mixin.15.1` — mixin widget in pipeline 15, instance 1
* `host-copier.0.playback` — host copier in pipeline 0, playback direction
* `dai-copier.1.ALH` — DAI copier in pipeline 1, ALH type

### Route Definitions

Routes connect FE pipeline mixin outputs to BE pipeline mixout inputs. This is the
primary mechanism for linking front-end and back-end pipelines:

```conf
Object.Base.route [
{
source "mixin.15.1"
sink "mixout.2.1"
}
]
```

Multiple FE pipelines can feed into a single BE mixout. For example, both a normal
playback pipeline and a deep buffer pipeline can route to the same DAI output:

```text
host-copier.0 -> gain.0 -> mixin.0 ─┐
├─> mixout.1 -> gain.1 -> dai-copier.1 -> DAI
host-copier.15 -> gain.15 -> mixin.15┘
```

### Platform Overrides

Platform-specific configurations are applied using the `IncludeByKey.PLATFORM` mechanism.
Each platform `.conf` file under `platform/intel/` contains `Define` blocks that override
variables such as `DMIC_DRIVER_VERSION`, `SSP_BLOB_VERSION`, and `NUM_HDMIS`.

Supported platforms:

* `tgl` — Tiger Lake / Alder Lake (CAVS 2.5)
* `mtl` — Meteor Lake (ACE 1.x)
* `lnl` — Lunar Lake (ACE 2.x)
* `ptl` — Panther Lake (ACE 3.x)

```conf
IncludeByKey.PLATFORM {
"mtl" "platform/intel/mtl.conf"
"lnl" "platform/intel/lnl.conf"
"ptl" "platform/intel/ptl.conf"
}
```

### Registering CMake Targets

Production topologies are registered in `production/tplg-targets-*.cmake` files. Each
target is a semicolon-separated tuple:

```text
"input-conf;output-name;variable1=value1,variable2=value2"
```

Select the cmake file matching the target platform generation:

| Platform | CMake Target File |
|---|---|
| Tiger Lake / Alder Lake | `tplg-targets-cavs25.cmake` |
| Meteor Lake | `tplg-targets-ace1.cmake` |
| Lunar Lake | `tplg-targets-ace2.cmake` |
| Panther Lake | `tplg-targets-ace3.cmake` |
| HDA generic | `tplg-targets-hda-generic.cmake` |

Development and testing topologies go in `development/tplg-targets.cmake`.

## Building Topologies

Configure the build with cmake and build the topology targets:

```bash
mkdir build && cd build
cmake ..
make -j$(nproc)
```

To build a specific topology target:

```bash
make sof-lnl-sdw-cs42l43-l0-cs35l56-l12
```

The compiled `.tplg` files are placed in the build output directory.
Loading