SoundWire 中的音频流¶
音频流是在以下各项之间创建的逻辑或虚拟连接:
系统内存缓冲区和编解码器
DSP 内存缓冲区和编解码器
FIFO 和编解码器
编解码器和编解码器
通常由 DMA 通道通过数据链路驱动。音频流包含一个或多个数据通道。流中的所有通道必须具有相同的采样率和相同的采样大小。
假设使用 SoundWire 接口打开一个具有两个通道(左声道和右声道)的流。以下是一些在 SoundWire 中表示流的方式。
内存(系统内存、DSP 内存或 FIFO)中的流采样
-------------------------
| L | R | L | R | L | R |
-------------------------
示例 1:具有 L 和 R 通道的立体声流从主设备渲染到从设备。主设备和从设备都使用单个端口。
+---------------+ Clock Signal +---------------+
| Master +----------------------------------+ Slave |
| Interface | | Interface |
| | | 1 |
| | Data Signal | |
| L + R +----------------------------------+ L + R |
| (Data) | Data Direction | (Data) |
+---------------+ +-----------------------> +---------------+
示例 2:具有 L 和 R 通道的立体声流从从设备捕获到主设备。主设备和从设备都使用单个端口。
+---------------+ Clock Signal +---------------+
| Master +----------------------------------+ Slave |
| Interface | | Interface |
| | | 1 |
| | Data Signal | |
| L + R +----------------------------------+ L + R |
| (Data) | Data Direction | (Data) |
+---------------+ <-----------------------+ +---------------+
示例 3:具有 L 和 R 通道的立体声流由主设备渲染。每个 L 和 R 通道由两个不同的从设备接收。主设备和两个从设备都使用单个端口。
+---------------+ Clock Signal +---------------+
| Master +---------+------------------------+ Slave |
| Interface | | | Interface |
| | | | 1 |
| | | Data Signal | |
| L + R +---+------------------------------+ L |
| (Data) | | | Data Direction | (Data) |
+---------------+ | | +-------------> +---------------+
| |
| |
| | +---------------+
| +----------------------> | Slave |
| | Interface |
| | 2 |
| | |
+----------------------------> | R |
| (Data) |
+---------------+
示例 4:具有 L 和 R 通道的立体声流由主设备渲染。两个 L 和 R 通道都由两个不同的从设备接收。主设备和两个从设备都使用处理 L+R 的单个端口。每个从设备在本地处理 L + R 数据,通常基于静态配置或动态方向,并且可以驱动一个或多个扬声器。
+---------------+ Clock Signal +---------------+
| Master +---------+------------------------+ Slave |
| Interface | | | Interface |
| | | | 1 |
| | | Data Signal | |
| L + R +---+------------------------------+ L + R |
| (Data) | | | Data Direction | (Data) |
+---------------+ | | +-------------> +---------------+
| |
| |
| | +---------------+
| +----------------------> | Slave |
| | Interface |
| | 2 |
| | |
+----------------------------> | L + R |
| (Data) |
+---------------+
示例 5:具有 L 和 R 通道的立体声流由主设备的两个不同端口渲染,并且仅由从设备接口的单个端口接收。
+--------------------+
| |
| +--------------+ +----------------+
| | || | |
| | Data Port || L Channel | |
| | 1 |------------+ | |
| | L Channel || | +-----+----+ |
| | (Data) || | L + R Channel || Data | |
| Master +----------+ | +---+---------> || Port | |
| Interface | | || 1 | |
| +--------------+ | || | |
| | || | +----------+ |
| | Data Port |------------+ | |
| | 2 || R Channel | Slave |
| | R Channel || | Interface |
| | (Data) || | 1 |
| +--------------+ Clock Signal | L + R |
| +---------------------------> | (Data) |
+--------------------+ | |
+----------------+
示例 6:具有 L 和 R 通道的立体声流由 2 个主设备渲染,每个主设备渲染一个通道,并由两个不同的从设备接收,每个从设备接收一个通道。两个主设备和两个从设备都使用单个端口。
+---------------+ Clock Signal +---------------+
| Master +----------------------------------+ Slave |
| Interface | | Interface |
| 1 | | 1 |
| | Data Signal | |
| L +----------------------------------+ L |
| (Data) | Data Direction | (Data) |
+---------------+ +-----------------------> +---------------+
+---------------+ Clock Signal +---------------+
| Master +----------------------------------+ Slave |
| Interface | | Interface |
| 2 | | 2 |
| | Data Signal | |
| R +----------------------------------+ R |
| (Data) | Data Direction | (Data) |
+---------------+ +-----------------------> +---------------+
示例 7:具有 L 和 R 通道的立体声流由 2 个主设备渲染,每个主设备都渲染两个通道。每个从设备接收 L + R。这与示例 4 的应用程序相同,但从设备放置在单独的链路上。
+---------------+ Clock Signal +---------------+
| Master +----------------------------------+ Slave |
| Interface | | Interface |
| 1 | | 1 |
| | Data Signal | |
| L + R +----------------------------------+ L + R |
| (Data) | Data Direction | (Data) |
+---------------+ +-----------------------> +---------------+
+---------------+ Clock Signal +---------------+
| Master +----------------------------------+ Slave |
| Interface | | Interface |
| 2 | | 2 |
| | Data Signal | |
| L + R +----------------------------------+ L + R |
| (Data) | Data Direction | (Data) |
+---------------+ +-----------------------> +---------------+
示例 8:4 通道流由 2 个主设备渲染,每个主设备渲染 2 个通道。每个从设备接收 2 个通道。
+---------------+ Clock Signal +---------------+
| Master +----------------------------------+ Slave |
| Interface | | Interface |
| 1 | | 1 |
| | Data Signal | |
| L1 + R1 +----------------------------------+ L1 + R1 |
| (Data) | Data Direction | (Data) |
+---------------+ +-----------------------> +---------------+
+---------------+ Clock Signal +---------------+
| Master +----------------------------------+ Slave |
| Interface | | Interface |
| 2 | | 2 |
| | Data Signal | |
| L2 + R2 +----------------------------------+ L2 + R2 |
| (Data) | Data Direction | (Data) |
+---------------+ +-----------------------> +---------------+
注 1:在上述多链路情况下,要锁定,需要获取全局锁,然后继续锁定总线实例。但是,在这种情况下,调用方框架 (ASoC DPCM) 保证卡上的流操作始终是串行化的。因此,不存在竞争条件,因此无需全局锁。
注 2:可以配置从设备以接收在给定流的链路上发送的所有通道(示例 4)或仅接收数据子集(示例 3)。从设备的配置不是由 SoundWire 子系统 API 处理的,而是由 snd_soc_dai_set_tdm_slot() API 处理的。平台或机器驱动程序通常会配置使用哪些槽。例如,对于示例 4,所有设备将使用相同的槽,而对于示例 3,从设备 1 将使用例如槽 0,从设备 2 将使用槽 1。
注 3:多个接收端口可以为 SoundWire 帧中的相同位槽提取相同的信息,但是多个发送端口应配置为不同的位槽配置。这与 I2S/PCM TDM 用法具有相同的限制。
SoundWire 流管理流程¶
流定义¶
当前流:这被分类为必须执行操作(如准备、启用、禁用、取消准备等)的流。
活动流:这被分类为总线上已激活的流,而不是当前流。总线上可以有多个活动流。
SoundWire 总线管理在 SoundWire 总线上渲染/捕获的每个流的流操作。本节介绍总线上为每个分配/释放的流完成的总线操作。以下是总线为每个音频流维护的流状态。
SoundWire 流状态¶
下面显示了 SoundWire 流状态和状态转换图。
+-----------+ +------------+ +----------+ +----------+
| ALLOCATED +---->| CONFIGURED +---->| PREPARED +---->| ENABLED |
| STATE | | STATE | | STATE | | STATE |
+-----------+ +------------+ +---+--+---+ +----+-----+
^ ^ ^
| | |
__| |___________ |
| | |
v | v
+----------+ +-----+------+ +-+--+-----+
| RELEASED |<----------+ DEPREPARED |<-------+ DISABLED |
| STATE | | STATE | | STATE |
+----------+ +------------+ +----------+
注:仅当 ALSA/ASoC 级别支持 INFO_PAUSE 标志时,SDW_STREAM_ENABLED
和 SDW_STREAM_DISABLED
之间的状态转换才相关。同样,SDW_DISABLED_STATE
和 SDW_PREPARED_STATE
之间的转换取决于 INFO_RESUME 标志。
注 2:该框架实现了基本的状态转换检查,但例如,不检查从 DISABLED 到 ENABLED 的转换在特定平台上是否有效。此类测试需要在 ALSA/ASoC 级别添加。
流状态操作¶
以下部分说明了作为流状态转换的一部分,总线在主设备和从设备上完成的操作。
SDW_STREAM_ALLOCATED¶
流的分配状态。这是流的初始状态。进入此状态之前执行的操作
为流分配流运行时。此流运行时用作对流执行的所有操作的参考。
分配并初始化用于保存流运行时信息的资源。这保存所有与流相关的信息,例如流类型 (PCM/PDM) 和参数、与流关联的主设备和从设备接口、流状态等。
在所有上述操作成功后,流状态设置为 SDW_STREAM_ALLOCATED
。
总线实现以下 API 来分配流,每个流需要调用一次。从 ASoC DPCM 框架来看,此流状态可能与 .startup() 操作相关联。
int sdw_alloc_stream(char * stream_name);
SoundWire 核心提供 sdw_startup_stream() 辅助函数,该函数通常在 dailink .startup() 回调期间调用,该函数执行流分配并为连接到流的所有 DAI 设置流指针。
SDW_STREAM_CONFIGURED¶
流的配置状态。进入此状态之前执行的操作
此处更新在 SDW_STREAM_ALLOCATED 状态下为流信息分配的资源。这包括流参数、与当前流关联的主设备和从设备运行时信息。
与当前流关联的所有主设备和从设备都向总线提供端口信息,其中包括主设备和从设备为当前流分配的端口号及其通道掩码。
在所有上述操作成功后,流状态设置为 SDW_STREAM_CONFIGURED
。
总线实现以下 API 用于 CONFIG 状态,该状态需要由与流关联的各自的主设备和从设备调用。这些 API 只能由各自的主设备和从设备调用一次。从 ASoC DPCM 框架来看,此流状态与 .hw_params() 操作相关联。
int sdw_stream_add_master(struct sdw_bus * bus,
struct sdw_stream_config * stream_config,
const struct sdw_ports_config * ports_config,
struct sdw_stream_runtime * stream);
int sdw_stream_add_slave(struct sdw_slave * slave,
struct sdw_stream_config * stream_config,
const struct sdw_ports_config * ports_config,
struct sdw_stream_runtime * stream);
SDW_STREAM_PREPARED¶
流的准备状态。进入此状态之前执行的操作
在恢复操作的情况下,省略步骤 1 和 2,因为总线带宽是已知的。
基于当前流以及总线上已激活的流计算总线参数,例如带宽、帧形状、时钟频率。需要重新计算以适应总线上的当前流。
根据步骤 1 中计算的帧形状和时钟频率,计算所有主设备和从设备端口的传输和端口参数,以用于当前以及已激活的流。
将计算出的总线和传输参数编程到主设备和从设备的寄存器中。交替库(当前未使用的库)上完成库寄存器编程。为不在交替库上的已激活流启用端口。这样做是为了不中断已激活的流。
一旦所有值都已编程,总线就会启动切换到交替库,其中所有新编程的值都会生效。
通过编程 PrepareCtrl 寄存器来准备当前流的主设备和从设备的端口。
在上述所有操作成功完成后,流状态将设置为 SDW_STREAM_PREPARED
。
总线为 PREPARE 状态实现了以下 API,每个流需要调用一次。从 ASoC DPCM 框架来看,此流状态与 .prepare() 操作相关联。由于 .trigger() 操作可能不遵循 .prepare(),因此允许从 SDW_STREAM_PREPARED
直接转换为 SDW_STREAM_DEPREPARED
。
int sdw_prepare_stream(struct sdw_stream_runtime * stream);
SDW_STREAM_ENABLED¶
流的使能状态。进入此状态后,数据端口将被启用。进入此状态之前执行的操作:
在 SDW_STREAM_PREPARED 状态下计算的所有值都将被编程到备用存储区(当前未使用的存储区)。它包括对已激活的流进行编程。
通过编程 ChannelEn 寄存器,将当前流的所有主端口和从端口在备用存储区(当前未使用的存储区)上启用。
一旦所有值都被编程,总线就会启动切换到备用存储区,所有新编程的值都会生效,并且与当前流关联的端口将被启用。
在上述所有操作成功完成后,流状态将设置为 SDW_STREAM_ENABLED
。
总线为 ENABLE 状态实现了以下 API,每个流需要调用一次。从 ASoC DPCM 框架来看,此流状态与 .trigger() 启动操作相关联。
int sdw_enable_stream(struct sdw_stream_runtime * stream);
SDW_STREAM_DISABLED¶
流的禁用状态。退出此状态后,数据端口将被禁用。进入此状态之前执行的操作:
通过编程 ChannelEn 寄存器,将当前流的所有主端口和从端口在备用存储区(当前未使用的存储区)上禁用。
总线和活动流的当前所有配置都被编程到备用存储区(当前未使用的存储区)。
一旦所有值都被编程,总线就会启动切换到备用存储区,所有新编程的值都会生效,并且与当前流关联的端口将被禁用。
在上述所有操作成功完成后,流状态将设置为 SDW_STREAM_DISABLED
。
总线为 DISABLED 状态实现了以下 API,每个流需要调用一次。从 ASoC DPCM 框架来看,此流状态与 .trigger() 停止操作相关联。
当支持 INFO_PAUSE 标志时,允许直接转换为 SDW_STREAM_ENABLED
。
对于 ASoC 将使用 .prepare() 回调的恢复操作,流可以从 SDW_STREAM_DISABLED
转换为 SDW_STREAM_PREPARED
,恢复所有必需的设置,但不更新带宽和位分配。
int sdw_disable_stream(struct sdw_stream_runtime * stream);
SDW_STREAM_DEPREPARED¶
流的取消准备状态。进入此状态之前执行的操作:
通过编程 PrepareCtrl 寄存器,取消准备当前流的所有主端口和从端口。
当前流的有效载荷带宽从总线总带宽需求中减少,并通过执行存储区切换等操作来计算和应用新参数。
在上述所有操作成功完成后,流状态将设置为 SDW_STREAM_DEPREPARED
。
总线为 DEPREPARED 状态实现了以下 API,每个流需要调用一次。ALSA/ASoC 没有“取消准备”的概念,并且此流状态到 ALSA/ASoC 操作的映射可能是特定于实现的。
当支持 INFO_PAUSE 标志时,流状态与 .hw_free() 操作相关联 - 流不会在 TRIGGER_STOP 时取消准备。
其他实现可能会在 TRIGGER_STOP 时转换为 SDW_STREAM_DEPREPARED
状态,如果它们需要通过 SDW_STREAM_PREPARED
状态转换。
int sdw_deprepare_stream(struct sdw_stream_runtime * stream);
SDW_STREAM_RELEASED¶
流的释放状态。进入此状态之前执行的操作:
释放与当前流关联的所有主端口和从端口的端口资源。
释放与当前流关联的主端口和从端口的运行时资源。
释放与当前流关联的流运行时资源。
在上述所有操作成功完成后,流状态将设置为 SDW_STREAM_RELEASED
。
总线为 RELEASE 状态实现了以下 API,所有与流关联的主端口和从端口都需要调用这些 API。从 ASoC DPCM 框架来看,此流状态与 .hw_free() 操作相关联。
int sdw_stream_remove_master(struct sdw_bus * bus,
struct sdw_stream_runtime * stream);
int sdw_stream_remove_slave(struct sdw_slave * slave,
struct sdw_stream_runtime * stream);
.shutdown() ASoC DPCM 操作调用以下总线 API,以释放分配为 ALLOCATED 状态的一部分的流。
在 .shutdown() 中,维护流状态的数据结构将被释放。
void sdw_release_stream(struct sdw_stream_runtime * stream);
SoundWire 核心提供了一个 sdw_shutdown_stream() 辅助函数,通常在 dailink .shutdown() 回调期间调用,它会清除连接到流的所有 DAIS 的流指针,并释放为该流分配的内存。
不支持¶
在两个流之间或跨流之间,不支持使用具有多个通道的单个端口。例如,即使理论上在 SoundWire 中是可能的,也不可以使用具有 4 个通道的端口来处理 2 个独立的立体声流。