From e1bcd55abee5d184d5e5ecab9a56daf6fb6769b8 Mon Sep 17 00:00:00 2001 From: Maurizio Casciano Date: Tue, 18 Aug 2026 22:46:47 +0200 Subject: [PATCH 1/2] topology2: ipc3: add Cherry Trail RT5677 topology Add topology2 support for generating and deploying IPC3 topology files alongside the existing IPC4 production set. Describe the Yoga Book RT5677 SSP2 playback, capture, low-latency and deep-buffer paths while preserving the Cherry Trail memory, scheduler and hardware configuration required by the legacy IPC3 firmware. Signed-off-by: Maurizio Casciano --- tools/topology/topology2/CMakeLists.txt | 14 + tools/topology/topology2/README.md | 20 ++ tools/topology/topology2/cht-rt5677.conf | 331 ++++++++++++++++++ .../topology2/include/components/buffer.conf | 4 + .../include/components/ipc3/mixer.conf | 28 ++ .../include/components/widget-common.conf | 13 + .../ipc3/mixer-volume-dai-playback.conf | 96 +++++ .../pipelines/ipc3/volume-dai-capture.conf | 98 ++++++ .../include/pipelines/pipeline-common.conf | 3 +- .../platform/intel/cht-hw-config.conf | 73 ++++ .../topology2/production/CMakeLists.txt | 24 ++ .../production/tplg-targets-ipc3.cmake | 7 + 12 files changed, 710 insertions(+), 1 deletion(-) create mode 100644 tools/topology/topology2/cht-rt5677.conf create mode 100644 tools/topology/topology2/include/components/ipc3/mixer.conf create mode 100644 tools/topology/topology2/include/pipelines/ipc3/mixer-volume-dai-playback.conf create mode 100644 tools/topology/topology2/include/pipelines/ipc3/volume-dai-capture.conf create mode 100644 tools/topology/topology2/platform/intel/cht-hw-config.conf create mode 100644 tools/topology/topology2/production/tplg-targets-ipc3.cmake diff --git a/tools/topology/topology2/CMakeLists.txt b/tools/topology/topology2/CMakeLists.txt index 2a1ee5483e7e..de152f1526d2 100644 --- a/tools/topology/topology2/CMakeLists.txt +++ b/tools/topology/topology2/CMakeLists.txt @@ -7,9 +7,11 @@ add_custom_target(topologies2 ALL # Create directory structure to be provided for deployment COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/target/sof-ipc4-tplg COMMAND ${CMAKE_COMMAND} -E create_symlink sof-ipc4-tplg ${CMAKE_CURRENT_BINARY_DIR}/target/sof-ace-tplg + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/target/sof-tplg COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/target/development # copy the topology files only to target COMMAND ${CMAKE_COMMAND} -E copy_if_different production/*.tplg ${CMAKE_CURRENT_BINARY_DIR}/target/sof-ipc4-tplg/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different production/ipc3/*.tplg ${CMAKE_CURRENT_BINARY_DIR}/target/sof-tplg/ COMMAND ${CMAKE_COMMAND} -E copy_if_different development/*.tplg ${CMAKE_CURRENT_BINARY_DIR}/target/development/ ) @@ -37,6 +39,18 @@ add_custom_target(abi_target DEPENDS abi.conf ) +# Atom platforms use IPC3 even when the source topology is written using the +# topology2 object model. Keep a separate ABI manifest so IPC3 and IPC4 output +# can be built together without changing the existing production topologies. +add_custom_command(OUTPUT abi-ipc3.conf + COMMAND > abi-ipc3.conf ${CMAKE_CURRENT_SOURCE_DIR}/get_abi.sh + ${SOF_ROOT_SOURCE_DIRECTORY} "ipc3" + DEPENDS ${SOF_ROOT_SOURCE_DIRECTORY}/src/include/kernel/abi.h +) +add_custom_target(abi_ipc3_target + DEPENDS abi-ipc3.conf +) + add_dependencies(topologies2 topology2_dev topology2_prod) add_subdirectory(development) add_subdirectory(production) diff --git a/tools/topology/topology2/README.md b/tools/topology/topology2/README.md index 759867a47d1c..ffaaf7e97057 100644 --- a/tools/topology/topology2/README.md +++ b/tools/topology/topology2/README.md @@ -371,6 +371,7 @@ variables such as `DMIC_DRIVER_VERSION`, `SSP_BLOB_VERSION`, and `NUM_HDMIS`. Supported platforms: +* `cht` — Intel Cherry Trail / Atom (IPC3; currently Yoga Book RT5677 only) * `tgl` — Intel Tiger Lake / Alder Lake (CAVS 2.5) * `mtl` — Intel Meteor Lake (ACE 1.x) * `lnl` — Intel Lunar Lake (ACE 2.x) @@ -397,10 +398,29 @@ Select the cmake file matching the target platform generation: | Platform | CMake Target File | |---|---| +| Cherry Trail IPC3 | `tplg-targets-ipc3.cmake` | | 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` | +### Cherry Trail IPC3 + +Topology2 describes the source format and does not require the target firmware +to use IPC4. Cherry Trail firmware uses IPC3, so its targets are built with a +separate IPC3 ABI manifest and installed under `target/sof-tplg` instead of the +IPC4/ACE topology directory. + +The Yoga Book topology can be built directly with: + +```bash +cmake --build . --target topology2_prod_ipc3_sof-cht-rt5677 +``` + +`sof-cht-rt5677.tplg` configures SSP2 for DSP_B with an inverted bit clock and +non-inverted frame clock, 48 kHz stereo S24_LE, four 25-bit TDM slots, a +4.8 MHz bit clock, and a 19.2 MHz codec MCLK. These values must stay aligned +with the Linux `cht-yogabook` machine driver. + Development and testing topologies go in `development/tplg-targets.cmake`. diff --git a/tools/topology/topology2/cht-rt5677.conf b/tools/topology/topology2/cht-rt5677.conf new file mode 100644 index 000000000000..7c10ceda871a --- /dev/null +++ b/tools/topology/topology2/cht-rt5677.conf @@ -0,0 +1,331 @@ +# SPDX-License-Identifier: BSD-3-Clause + + + + + + + + + + + + + + + + + + + + + + + + +Define { + # Cherry Trail uses the first-generation SSP configuration blob and IPC3. + SSP_BLOB_VERSION $SSP_BLOB_VERSION_1_0 +} + +# The machine driver connects the RT5677 AIF routes through these platform +# widgets. They mirror the widgets exposed by the legacy Cherry Trail topology. +Object.Widget.virtual [ + { name "codec_out0" type "output" index 1 } + { name "codec_out1" type "output" index 2 } + { name "codec_in0" type "input" index 3 } + { name "codec_in1" type "input" index 4 } + { name "ssp2 Rx" type "input" index 5 } + { name "ssp2 Tx" type "output" index 6 } +] + +# SSP2 carries the RT5677 AIF1 stream. The bit and frame clocks are driven by +# the Cherry Trail SSP; the codec consumes both clocks. +Object.Dai.SSP [ + { + id 0 + dai_index 2 + direction "duplex" + name "SSP2-Codec" + default_hw_conf_id 0 + sample_bits 24 + + Object.Base.hw_config.1 { + name "SSP2" + id 0 + format "DSP_B" + mclk "codec_mclk_in" + mclk_freq 19200000 + bclk "codec_consumer" + bclk_freq 4800000 + # Match SND_SOC_DAIFMT_IB_NF in the machine driver: + # invert BCLK while leaving the frame clock non-inverted. + bclk_invert "true" + fsync "codec_consumer" + fsync_freq 48000 + fsync_invert "false" + tdm_slots 4 + tdm_slot_width 25 + tx_slots 15 + rx_slots 15 + } + } +] + +Object.Pipeline { + ipc3-mixer-volume-dai-playback.1 { + index 1 + format "s24le" + channels 2 + dai_type "SSP" + dai_index 2 + periods 2 + # Two 1 ms periods at 48 kHz, stereo, four bytes per sample. + size 768 + + Object.Widget.pipeline.1 { + stream_name "dai.SSP.2.playback" + priority 1 + core 0 + frames 0 + mips 5000 + } + Object.Widget.dai.playback { + format "s24le" + stream_name "SSP2-Codec" + core_id 0 + } + Object.Widget.pga.1 { + ramp_step_ms 20 + Object.Control.mixer.1 { + name "1 Master Playback Volume" + Object.Base.tlv.vtlv_m64s2 { + Object.Base.scale.m64s2 {} + } + } + } + Object.Base.route.4 { + source "buffer.1.2" + sink "dai.SSP.2.playback" + } + } + + ipc3-volume-dai-capture.2 { + index 2 + format "s32le" + channels 2 + dai_type "SSP" + dai_index 2 + periods 2 + # Two 1 ms periods at 48 kHz, stereo, four bytes per sample. + size 768 + + Object.Widget.pipeline.1 { + stream_name "dai.SSP.2.capture" + priority 0 + core 0 + frames 0 + mips 5000 + } + Object.Widget.dai.capture { + format "s24le" + stream_name "SSP2-Codec" + core_id 0 + } + Object.Widget.host.capture { + stream_name "Low Latency Capture 0" + core_id 0 + } + Object.Widget.pga.1 { + ramp_step_ms 250 + Object.Control.mixer.1 { + name "2 PCM 0 Capture Volume" + max 40 + Object.Base.channel.1 { + name "fl" + reg 0 + shift 0 + } + Object.Base.channel.2 { + name "fr" + reg 0 + shift 1 + } + Object.Base.tlv.vtlv_m64s2 { + Object.Base.scale.m64s2 {} + } + } + } + Object.Base.route.4 { + source "dai.SSP.2.capture" + sink "buffer.2.1" + } + } + + volume-playback.3 { + index 3 + format "s32le" + channels 2 + period 1000 + time_domain "dma" + + Object.Widget.pipeline.1 { + priority 0 + core 0 + frames 0 + mips 100000 + stream_name "dai.SSP.2.playback" + } + Object.Widget.host.playback { + stream_name "Playback 0" + core_id 0 + period_sink_count 0 + period_source_count 2 + } + Object.Widget.pga.1 { + ramp_step_ms 20 + period_sink_count 2 + period_source_count 2 + Object.Control.mixer.1 { + name "3 Playback Volume" + Object.Base.tlv.vtlv_m64s2 { + Object.Base.scale.m64s2 {} + } + } + } + Object.Widget.buffer.1 { + caps "ram_dma_cache" + # IPC3 needs SOF_TKN_BUF_SIZE serialized explicitly. An object + # value takes precedence over the class's automatic size value. + size 768 + } + Object.Widget.buffer.2 { + caps "ram_dma_cache" + size 768 + } + } + + volume-playback.4 { + index 4 + format "s32le" + channels 2 + period 5000 + time_domain "dma" + + Object.Widget.pipeline.1 { + priority 0 + core 0 + frames 0 + mips 100000 + stream_name "dai.SSP.2.playback" + } + Object.Widget.host.playback { + stream_name "Playback 1" + core_id 0 + period_sink_count 0 + period_source_count 2 + } + Object.Widget.pga.1 { + ramp_step_ms 20 + period_sink_count 2 + period_source_count 2 + Object.Control.mixer.1 { + name "4 Playback Volume" + Object.Base.tlv.vtlv_m64s2 { + Object.Base.scale.m64s2 {} + } + } + } + Object.Widget.buffer.1 { + caps "ram_dma_cache" + # Five milliseconds at 48 kHz, two channels and four bytes per + # sample, with two periods: 5 ms * 48 * 2 * 4 * 2 = 3840. + size 3840 + } + Object.Widget.buffer.2 { + caps "ram_dma_cache" + size 3840 + } + } +} + +# Both playback front ends feed the low-latency SSP2 mixer. +Object.Base.route [ + { source "buffer.3.2" sink "ipc3-mixer.1.1" } + { source "buffer.4.2" sink "ipc3-mixer.1.1" } +] + +Object.PCM.pcm [ + { + name "PCM" + id 0 + direction "duplex" + + Object.Base.fe_dai.1 { + name "PCM 0" + } + Object.PCM.pcm_caps.playback { + direction "playback" + name "Playback 0" + formats "S16_LE,S24_LE,S32_LE" + rates "48000" + rate_min 48000 + rate_max 48000 + channels_min 2 + channels_max 2 + # Match the proven Cherry Trail IPC3 topology instead of the + # much larger generic topology2 PCM capability defaults. + periods_min 2 + periods_max 16 + period_size_min 192 + period_size_max 16384 + buffer_size_min 65536 + buffer_size_max 65536 + } + Object.PCM.pcm_caps.capture { + direction "capture" + name "Low Latency Capture 0" + formats "S16_LE,S24_LE,S32_LE" + rates "48000" + rate_min 48000 + rate_max 48000 + channels_min 2 + channels_max 2 + periods_min 2 + # The low-latency capture path supports fewer queued periods + # than playback in the legacy Cherry Trail topology. + periods_max 4 + period_size_min 192 + period_size_max 16384 + buffer_size_min 65536 + buffer_size_max 65536 + } + } + { + name "PCM Deep Buffer" + id 1 + direction "playback" + + Object.Base.fe_dai.1 { + name "PCM Deep Buffer 1" + } + Object.PCM.pcm_caps.playback { + direction "playback" + name "Playback 1" + formats "S16_LE,S24_LE,S32_LE" + rates "48000" + rate_min 48000 + rate_max 48000 + channels_min 2 + channels_max 2 + # Keep userspace constraints identical for the low-latency and + # deep-buffer playback PCMs, as on the legacy CHT topology. + periods_min 2 + periods_max 16 + period_size_min 192 + period_size_max 16384 + buffer_size_min 65536 + buffer_size_max 65536 + } + } +] diff --git a/tools/topology/topology2/include/components/buffer.conf b/tools/topology/topology2/include/components/buffer.conf index 32dfcebd29f8..66a03a683458 100644 --- a/tools/topology/topology2/include/components/buffer.conf +++ b/tools/topology/topology2/include/components/buffer.conf @@ -60,12 +60,16 @@ Class.Widget."buffer" { "host" "pass" "comp" + # RAM | DMA | CACHE, without the HP capability required by + # newer Intel platforms. + "ram_dma_cache" ] !tuple_values [ 113 113 113 65 + 97 ] } } diff --git a/tools/topology/topology2/include/components/ipc3/mixer.conf b/tools/topology/topology2/include/components/ipc3/mixer.conf new file mode 100644 index 000000000000..e04596960b4d --- /dev/null +++ b/tools/topology/topology2/include/components/ipc3/mixer.conf @@ -0,0 +1,28 @@ +## \struct ipc3_mixer +## \brief IPC3 low-latency mixer widget. + +Class.Widget."ipc3-mixer" { + DefineAttribute."index" {} + DefineAttribute."instance" {} + + + + attributes { + !constructor [ + "index" + "instance" + ] + !mandatory [ + "format" + ] + !immutable [ + "type" + "uuid" + ] + unique "instance" + } + + type "mixer" + uuid "37:c0:06:bc:aa:12:7c:41:9a:97:89:28:2e:32:1a:76" + no_pm "true" +} diff --git a/tools/topology/topology2/include/components/widget-common.conf b/tools/topology/topology2/include/components/widget-common.conf index 14ccd8fcf86a..a132110805fb 100644 --- a/tools/topology/topology2/include/components/widget-common.conf +++ b/tools/topology/topology2/include/components/widget-common.conf @@ -68,6 +68,19 @@ DefineAttribute."core_id" { token_ref "comp.word" } +## Number of periods available at the component's sink. +## +## These IPC3 attributes are optional so IPC4 objects remain unchanged unless +## a topology explicitly sets them. +DefineAttribute."period_sink_count" { + token_ref "comp.word" +} + +## Number of periods available at the component's source. +DefineAttribute."period_source_count" { + token_ref "comp.word" +} + ## number of periods to preload DefineAttribute."preload_count" { # Token set reference name and type diff --git a/tools/topology/topology2/include/pipelines/ipc3/mixer-volume-dai-playback.conf b/tools/topology/topology2/include/pipelines/ipc3/mixer-volume-dai-playback.conf new file mode 100644 index 000000000000..0c4c31cc694a --- /dev/null +++ b/tools/topology/topology2/include/pipelines/ipc3/mixer-volume-dai-playback.conf @@ -0,0 +1,96 @@ +## \struct ipc3_mixer_volume_dai_playback +## \brief IPC3 mixer, volume and DAI playback pipeline. +## +## Multiple host pipelines can connect to the mixer endpoint. The pipeline is +## DMA scheduled by its backend DAI and uses a PGA immediately before that DAI. + + + + + + + + +Class.Pipeline."ipc3-mixer-volume-dai-playback" { + + + # The backend type/index and buffer geometry are inherited by the DAI and + # buffer child objects. The topology connects the final buffer to the + # selected backend endpoint outside this reusable class. + DefineAttribute."dai_type" { + type "string" + } + DefineAttribute."dai_index" {} + DefineAttribute."periods" {} + DefineAttribute."size" {} + + attributes { + !constructor [ + "index" + ] + !mandatory [ + "format" + "dai_type" + "dai_index" + "periods" + "size" + ] + !immutable [ + "direction" + ] + unique "instance" + } + + Object.Widget { + pipeline."1" {} + + ipc3-mixer."1" { + period_sink_count 2 + period_source_count 2 + } + + buffer."1" { + caps "comp" + } + + pga."1" { + period_sink_count 2 + period_source_count 2 + ramp_step_ms 20 + } + + buffer."2" { + caps "comp" + } + + dai."playback" { + type "dai_in" + direction "playback" + period_sink_count 0 + period_source_count 2 + } + } + + Object.Base { + route."1" { + source "ipc3-mixer.$index.1" + sink "buffer.$index.1" + } + route."2" { + source "buffer.$index.1" + sink "pga.$index.1" + } + route."3" { + source "pga.$index.1" + sink "buffer.$index.2" + } + } + + direction "playback" + time_domain "dma" + period 1000 + priority 1 + core 0 + frames 0 + mips 5000 +} diff --git a/tools/topology/topology2/include/pipelines/ipc3/volume-dai-capture.conf b/tools/topology/topology2/include/pipelines/ipc3/volume-dai-capture.conf new file mode 100644 index 000000000000..aa96e70bee38 --- /dev/null +++ b/tools/topology/topology2/include/pipelines/ipc3/volume-dai-capture.conf @@ -0,0 +1,98 @@ +## \struct ipc3_volume_dai_capture +## \brief IPC3 DAI, volume and host capture pipeline. +## +## The backend DAI schedules the complete capture path. Keeping the DAI and host +## widgets in one pipeline avoids creating two scheduler widgets with the same +## pipeline ID, as was possible with the legacy m4 topology macros. + + + + + + + + +Class.Pipeline."ipc3-volume-dai-capture" { + + + # The backend type/index and buffer geometry are inherited by the DAI and + # buffer child objects. The topology connects the selected backend endpoint + # to the first buffer outside this reusable class. + DefineAttribute."dai_type" { + type "string" + } + DefineAttribute."dai_index" {} + DefineAttribute."periods" {} + DefineAttribute."size" {} + + attributes { + !constructor [ + "index" + ] + !mandatory [ + "format" + "dai_type" + "dai_index" + "periods" + "size" + ] + !immutable [ + "direction" + ] + unique "instance" + } + + Object.Widget { + pipeline."1" {} + + dai."capture" { + type "dai_out" + direction "capture" + period_sink_count 2 + period_source_count 0 + } + + buffer."1" { + caps "ram_dma_cache" + } + + pga."1" { + period_sink_count 2 + period_source_count 2 + ramp_step_ms 250 + } + + buffer."2" { + caps "ram_dma_cache" + } + + host."capture" { + type "aif_out" + period_sink_count 2 + period_source_count 0 + } + } + + Object.Base { + route."1" { + source "buffer.$index.1" + sink "pga.$index.1" + } + route."2" { + source "pga.$index.1" + sink "buffer.$index.2" + } + route."3" { + source "buffer.$index.2" + sink "host.$index.capture" + } + } + + direction "capture" + time_domain "dma" + period 1000 + priority 0 + core 0 + frames 0 + mips 5000 +} diff --git a/tools/topology/topology2/include/pipelines/pipeline-common.conf b/tools/topology/topology2/include/pipelines/pipeline-common.conf index 37a34066d4df..260088de45a3 100644 --- a/tools/topology/topology2/include/pipelines/pipeline-common.conf +++ b/tools/topology/topology2/include/pipelines/pipeline-common.conf @@ -46,7 +46,8 @@ DefineAttribute."period" { token_ref "scheduler.word" constraints { min 333 - max 1000 + # The upper bound is platform-specific. IPC3 topologies commonly + # use periods longer than the 1 ms typical of IPC4 pipelines. } } diff --git a/tools/topology/topology2/platform/intel/cht-hw-config.conf b/tools/topology/topology2/platform/intel/cht-hw-config.conf new file mode 100644 index 000000000000..3db7ffb7ccd8 --- /dev/null +++ b/tools/topology/topology2/platform/intel/cht-hw-config.conf @@ -0,0 +1,73 @@ +## \struct hw_config +## \brief Cherry Trail SSP hardware configuration. +## +## Cherry Trail supports SSP clocks outside the cardinal-clock frequencies +## accepted by the modern CAVS hardware configuration class. Device-specific +## clocking and slot geometry belong in the topology object that uses this +## class. + +Class.Base."hw_config" { + DefineAttribute."id" {} + DefineAttribute."instance" {} + + DefineAttribute."name" { + type "string" + } + + DefineAttribute."format" { + type "string" + constraints { + !valid_values [ + "I2S" + "DSP_A" + "DSP_B" + ] + } + } + + DefineAttribute."mclk" { + type "string" + } + DefineAttribute."mclk_freq" {} + DefineAttribute."bclk" { + type "string" + } + DefineAttribute."bclk_freq" {} + DefineAttribute."bclk_invert" { + type "string" + constraints { + !valid_values [ + "true" + "false" + ] + } + } + DefineAttribute."fsync" { + type "string" + } + DefineAttribute."fsync_freq" {} + DefineAttribute."fsync_invert" { + type "string" + constraints { + !valid_values [ + "true" + "false" + ] + } + } + DefineAttribute."tdm_slots" {} + DefineAttribute."tdm_slot_width" {} + DefineAttribute."tx_slots" {} + DefineAttribute."rx_slots" {} + + attributes { + !constructor [ + "id" + ] + !mandatory [ + "name" + ] + unique "instance" + } + +} diff --git a/tools/topology/topology2/production/CMakeLists.txt b/tools/topology/topology2/production/CMakeLists.txt index c2cb86400e4b..06cf2b5a30b6 100644 --- a/tools/topology/topology2/production/CMakeLists.txt +++ b/tools/topology/topology2/production/CMakeLists.txt @@ -8,6 +8,7 @@ include(tplg-targets-ace2.cmake) include(tplg-targets-ace3.cmake) include(tplg-targets-ace4.cmake) include(tplg-targets-imx8.cmake) +include(tplg-targets-ipc3.cmake) add_custom_target(topology2_prod) @@ -32,3 +33,26 @@ foreach(tplg ${TPLGS}) add_custom_target(topology2_prod_${output} DEPENDS ${output}.tplg) add_dependencies(topology2_prod topology2_prod_${output}) endforeach() + +# IPC3 topologies are kept in a separate build subdirectory so the deployment +# step cannot accidentally install them as IPC4/ACE topology files. +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ipc3) + +foreach(tplg ${IPC3_TPLGS}) + set(defines "") + list(LENGTH tplg length) + list(GET tplg 0 input) + list(GET tplg 1 output) + + math(EXPR last_index "${length}-1") + if (${last_index} EQUAL 2) + list(GET tplg ${last_index} defines) + endif() + + add_alsatplg2_command("${CMAKE_CURRENT_BINARY_DIR}/../abi-ipc3.conf" abi_ipc3_target + "${CMAKE_CURRENT_SOURCE_DIR}/../${input}" "ipc3/${output}" + "${CMAKE_CURRENT_SOURCE_DIR}/../" "${defines}") + + add_custom_target(topology2_prod_ipc3_${output} DEPENDS ipc3/${output}.tplg) + add_dependencies(topology2_prod topology2_prod_ipc3_${output}) +endforeach() diff --git a/tools/topology/topology2/production/tplg-targets-ipc3.cmake b/tools/topology/topology2/production/tplg-targets-ipc3.cmake new file mode 100644 index 000000000000..b7949b9bd70f --- /dev/null +++ b/tools/topology/topology2/production/tplg-targets-ipc3.cmake @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: BSD-3-Clause + +# Topology2 source targeting firmware that uses the SOF IPC3 protocol. +set(IPC3_TPLGS + # Lenovo Yoga Book YB1-X91F/L: Cherry Trail SSP2 with RT5677 AIF1. + "cht-rt5677\;sof-cht-rt5677" +) From 22f080bc3b359bacd0bc6a13e689d98706d44a20 Mon Sep 17 00:00:00 2001 From: Maurizio Casciano Date: Thu, 20 Aug 2026 01:27:03 +0200 Subject: [PATCH 2/2] topology2: ipc3: fix Yoga Book stereo slot masks SOF IPC3 derives the DAI channel count from the population count of the active TDM slot mask. The original topology enabled all four frame slots with mask 0xf while exposing two-channel PCM streams, so STREAM_PCM_PARAMS failed with -EINVAL. Keep the required four-slot, 25-bit frame geometry but activate only the two stereo slots with mask 0x3. Also use the defined default_hw_config_id attribute and add an artifact verifier for the SSP clocks, format, frame geometry, sample width and PCM capabilities. Tested on a Lenovo Yoga Book YB1-X91L with PCM0 playback and capture in S16_LE, S24_LE and S32_LE at 48 kHz stereo, plus PCM1 deep-buffer playback. Signed-off-by: Maurizio Casciano --- tools/topology/topology2/README.md | 5 +- tools/topology/topology2/cht-rt5677.conf | 6 +- tools/topology/topology2/verify-cht-rt5677.py | 231 ++++++++++++++++++ 3 files changed, 238 insertions(+), 4 deletions(-) create mode 100755 tools/topology/topology2/verify-cht-rt5677.py diff --git a/tools/topology/topology2/README.md b/tools/topology/topology2/README.md index ffaaf7e97057..6d5c99e0eff8 100644 --- a/tools/topology/topology2/README.md +++ b/tools/topology/topology2/README.md @@ -416,11 +416,14 @@ The Yoga Book topology can be built directly with: ```bash cmake --build . --target topology2_prod_ipc3_sof-cht-rt5677 +python3 ../tools/topology/topology2/verify-cht-rt5677.py \ + topology/topology2/production/ipc3/sof-cht-rt5677.tplg ``` `sof-cht-rt5677.tplg` configures SSP2 for DSP_B with an inverted bit clock and non-inverted frame clock, 48 kHz stereo S24_LE, four 25-bit TDM slots, a 4.8 MHz bit clock, and a 19.2 MHz codec MCLK. These values must stay aligned -with the Linux `cht-yogabook` machine driver. +with the Linux `cht-yogabook` machine driver. The verifier parses the compiled +artifact and rejects changes to the SSP link or the PCM0/PCM1 stereo contract. Development and testing topologies go in `development/tplg-targets.cmake`. diff --git a/tools/topology/topology2/cht-rt5677.conf b/tools/topology/topology2/cht-rt5677.conf index 7c10ceda871a..96c18861a78b 100644 --- a/tools/topology/topology2/cht-rt5677.conf +++ b/tools/topology/topology2/cht-rt5677.conf @@ -47,7 +47,7 @@ Object.Dai.SSP [ dai_index 2 direction "duplex" name "SSP2-Codec" - default_hw_conf_id 0 + default_hw_config_id 0 sample_bits 24 Object.Base.hw_config.1 { @@ -66,8 +66,8 @@ Object.Dai.SSP [ fsync_invert "false" tdm_slots 4 tdm_slot_width 25 - tx_slots 15 - rx_slots 15 + tx_slots 3 + rx_slots 3 } } ] diff --git a/tools/topology/topology2/verify-cht-rt5677.py b/tools/topology/topology2/verify-cht-rt5677.py new file mode 100755 index 000000000000..2a309ea3c77f --- /dev/null +++ b/tools/topology/topology2/verify-cht-rt5677.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +"""Verify the compiled Cherry Trail RT5677 topology contract.""" + +import argparse +import struct +import sys +from pathlib import Path + + +TPLG_MAGIC = 0x41536F43 +TPLG_ABI = 5 +TYPE_PCM = 7 +TYPE_BACKEND_LINK = 10 +HEADER = struct.Struct("<9I") +STREAM_CAPS_SIZE = 104 +PCM_SIZE = 912 +LINK_SIZE = 1656 +HW_CONFIG_SIZE = 120 + + +class VerificationError(Exception): + """Report a topology contract violation.""" + + +def u32(data: bytes, offset: int) -> int: + """Read one little-endian unsigned 32-bit value.""" + return struct.unpack_from(" str: + """Read one fixed-width, NUL-terminated topology string.""" + return data[offset : offset + size].split(b"\0", 1)[0].decode("ascii") + + +def blocks(data: bytes): + """Yield validated topology block headers and payloads.""" + offset = 0 + while offset < len(data): + if len(data) - offset < HEADER.size: + raise VerificationError(f"truncated block header at offset {offset}") + magic, abi, _, block_type, size, _, payload_size, _, count = HEADER.unpack_from( + data, offset + ) + if magic != TPLG_MAGIC: + raise VerificationError(f"invalid topology magic at offset {offset}") + if abi != TPLG_ABI: + raise VerificationError(f"expected topology ABI {TPLG_ABI}, found {abi}") + if size != HEADER.size: + raise VerificationError(f"unexpected block header size {size}") + payload_start = offset + size + payload_end = payload_start + payload_size + if payload_end > len(data): + raise VerificationError(f"truncated block payload at offset {offset}") + yield block_type, count, data[payload_start:payload_end] + offset = payload_end + + if offset != len(data): + raise VerificationError("topology has trailing bytes") + + +def records(payload: bytes, count: int, fixed_size: int): + """Yield records whose private data follows the fixed topology structure.""" + offset = 0 + for _ in range(count): + if len(payload) - offset < fixed_size: + raise VerificationError("truncated topology record") + record_size = u32(payload, offset) + if record_size != fixed_size: + raise VerificationError( + f"unexpected topology record size {record_size}, expected {fixed_size}" + ) + private_size = u32(payload, offset + fixed_size - 4) + end = offset + fixed_size + private_size + if end > len(payload): + raise VerificationError("truncated topology private data") + yield payload[offset:end] + offset = end + + if offset != len(payload): + raise VerificationError("topology block contains trailing record data") + + +def verify_pcm(record: bytes, expected): + """Verify a PCM identity, directions, and its active stream capabilities.""" + pcm_name, pcm_id, playback, capture = expected + actual_name = c_string(record, 4) + actual_id = u32(record, 92) + actual_playback = u32(record, 100) + actual_capture = u32(record, 104) + if (actual_name, actual_id, actual_playback, actual_capture) != expected: + raise VerificationError( + f"unexpected PCM: {(actual_name, actual_id, actual_playback, actual_capture)}" + ) + + for direction, enabled, offset in ( + ("playback", playback, 692), + ("capture", capture, 692 + STREAM_CAPS_SIZE), + ): + caps_size = u32(record, offset) + if not enabled: + if caps_size != 0: + raise VerificationError(f"{pcm_name} unexpectedly enables {direction}") + continue + if caps_size != STREAM_CAPS_SIZE: + raise VerificationError(f"{pcm_name} has invalid {direction} capabilities") + rate_min = u32(record, offset + 60) + rate_max = u32(record, offset + 64) + channels_min = u32(record, offset + 68) + channels_max = u32(record, offset + 72) + if (rate_min, rate_max, channels_min, channels_max) != (48000, 48000, 2, 2): + raise VerificationError( + f"{pcm_name} {direction} is not fixed at 48 kHz stereo" + ) + + +def verify_link(record: bytes): + """Verify the single SSP2 hardware configuration.""" + if c_string(record, 8) != "SSP2-Codec": + raise VerificationError(f"unexpected backend link {c_string(record, 8)!r}") + if u32(record, 1636) != 1: + raise VerificationError("SSP2-Codec must contain exactly one hardware configuration") + if u32(record, 1640) != 0: + raise VerificationError("SSP2-Codec default hardware configuration must be ID 0") + + hw = record[676 : 676 + HW_CONFIG_SIZE] + if u32(hw, 0) != HW_CONFIG_SIZE or u32(hw, 4) != 0: + raise VerificationError("invalid SSP2 hardware configuration header") + actual = { + "format": u32(hw, 8), + "invert_bclk": hw[13], + "invert_fsync": hw[14], + "bclk_provider": hw[15], + "fsync_provider": hw[16], + "mclk_direction": hw[17], + "mclk_rate": u32(hw, 20), + "bclk_rate": u32(hw, 24), + "fsync_rate": u32(hw, 28), + "tdm_slots": u32(hw, 32), + "tdm_slot_width": u32(hw, 36), + "tx_slots": u32(hw, 40), + "rx_slots": u32(hw, 44), + } + expected = { + "format": 5, # SND_SOC_DAI_FORMAT_DSP_B + "invert_bclk": 1, + "invert_fsync": 0, + "bclk_provider": 1, + "fsync_provider": 1, + "mclk_direction": 1, + "mclk_rate": 19_200_000, + "bclk_rate": 4_800_000, + "fsync_rate": 48_000, + "tdm_slots": 4, + "tdm_slot_width": 25, + "tx_slots": 0x3, + "rx_slots": 0x3, + } + if actual != expected: + differences = ", ".join( + f"{key}={actual[key]!r} (expected {value!r})" + for key, value in expected.items() + if actual[key] != value + ) + raise VerificationError(f"invalid SSP2 hardware configuration: {differences}") + + private_size = u32(record, LINK_SIZE - 4) + private = record[LINK_SIZE : LINK_SIZE + private_size] + offset = 0 + sample_bits = [] + while offset < len(private): + if len(private) - offset < 12: + raise VerificationError("truncated SSP2 vendor array") + array_size, tuple_type, count = struct.unpack_from(" len(private): + raise VerificationError("invalid SSP2 vendor array size") + element_size = 48 if tuple_type == 1 else 8 + if 12 + count * element_size != array_size: + raise VerificationError("invalid SSP2 vendor array element count") + for element in range(count): + element_offset = offset + 12 + element * element_size + token = u32(private, element_offset) + if token == 502: + sample_bits.append(u32(private, element_offset + 4)) + offset += array_size + if sample_bits != [24]: + raise VerificationError(f"SSP2 valid sample bits must be 24, found {sample_bits}") + + +def verify(path: Path) -> None: + """Verify all Yoga Book topology invariants in a compiled artifact.""" + data = path.read_bytes() + if not data: + raise VerificationError("topology artifact is empty") + + pcm_records = [] + link_records = [] + for block_type, count, payload in blocks(data): + if block_type == TYPE_PCM: + pcm_records.extend(records(payload, count, PCM_SIZE)) + elif block_type == TYPE_BACKEND_LINK: + link_records.extend(records(payload, count, LINK_SIZE)) + + if len(link_records) != 1: + raise VerificationError( + f"expected one SSP2-Codec backend link, found {len(link_records)}" + ) + if len(pcm_records) != 2: + raise VerificationError(f"expected two PCMs, found {len(pcm_records)}") + + verify_link(link_records[0]) + verify_pcm(pcm_records[0], ("PCM", 0, 1, 1)) + verify_pcm(pcm_records[1], ("PCM Deep Buffer", 1, 1, 0)) + + +def main() -> int: + """Parse arguments and report a concise verification result.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("topology", type=Path, help="compiled sof-cht-rt5677.tplg") + args = parser.parse_args() + try: + verify(args.topology) + except (OSError, UnicodeDecodeError, struct.error, VerificationError) as error: + print(f"FAIL: {error}", file=sys.stderr) + return 1 + print(f"PASS: {args.topology} matches the Yoga Book SSP2 and stereo PCM contract") + return 0 + + +if __name__ == "__main__": + sys.exit(main())