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:多个 Sink 端口可以从 SoundWire 帧中相同的 bitSlots 提取相同的信息,但是多个 Source 端口应配置不同的 bitSlot 配置。这与 I2S/PCM TDM 用法具有相同的限制。
SoundWire 流管理流程¶
流定义¶
当前流:指需要执行操作的流,如准备、启用、禁用、去准备等。
活动流:指除当前流之外,已在总线上处于活动状态的流。总线上可以有多个活动流。
SoundWire 总线管理 SoundWire 总线上每个渲染/捕获流的流操作。本节解释了总线为总线上分配/释放的每个流执行的总线操作。以下是总线为每个音频流维护的流状态。
SoundWire 流状态¶
下图显示了 SoundWire 流状态和状态转换图。
+-----------+ +------------+ +----------+ +----------+
| ALLOCATED +---->| CONFIGURED +---->| PREPARED +---->| ENABLED |
| STATE | | STATE | | STATE | | STATE |
+-----------+ +------------+ +---+--+---+ +----+-----+
^ ^ ^
| | |
__| |___________ |
| | |
v | v
+----------+ +-----+------+ +-+--+-----+
| RELEASED |<----------+ DEPREPARED |<-------+ DISABLED |
| STATE | | STATE | | STATE |
+----------+ +------------+ +----------+
注意:SDW_STREAM_ENABLED
和 SDW_STREAM_DISABLED
之间的状态转换仅在 ALSA/ASoC 级别支持 INFO_PAUSE 标志时才相关。同样,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, enum sdw_stream_type type);
SoundWire 核心提供了一个 sdw_startup_stream() 辅助函数,通常在 dailink .startup() 回调期间调用,它执行流分配并为连接到流的所有 DAI 设置流指针。
SDW_STREAM_CONFIGURED¶
流的配置状态。在此状态之前执行的操作:
在此更新 SDW_STREAM_ALLOCATED 状态中为流信息分配的资源。这包括流参数、与当前流关联的主设备和从设备运行时信息。
与当前流关联的所有主设备和从设备都向总线提供端口信息,其中包括主设备和从设备为当前流分配的端口号及其通道掩码。
所有上述操作成功后,流状态设置为 SDW_STREAM_CONFIGURED
。
总线实现了用于 CONFIG 状态的以下 API,这些 API 需要由与流关联的相应主设备和从设备调用。这些 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 中计算的帧形状和时钟频率,计算所有主设备和从设备端口的传输和端口参数,包括当前流和已有的活动流。
计算出的总线和传输参数被编程到主设备和从设备的寄存器中。 banked 寄存器编程在备用 bank(当前未使用的 bank)上进行。已有的活动流的端口在备用 bank(当前未使用的 bank)上启用。这样做是为了不中断已有的活动流。
一旦所有值都编程完毕,总线将启动切换到备用 bank,所有新编程的值将在此生效。
通过编程 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 状态中计算出的所有值都被编程到备用 bank(当前未使用的 bank)中。这也包括已有的活动流的编程。
通过编程 ChannelEn 寄存器,在备用 bank(当前未使用的 bank)上启用当前流的所有主设备和从设备端口。
一旦所有值都编程完毕,总线将启动切换到备用 bank,所有新编程的值将在此生效,并且与当前流关联的端口将被启用。
所有上述操作成功后,流状态设置为 SDW_STREAM_ENABLED
。
总线实现了用于 ENABLE 状态的以下 API,每个流需要调用一次。从 ASoC DPCM 框架来看,此流状态与 .trigger() 启动操作相关联。
int sdw_enable_stream(struct sdw_stream_runtime * stream);
SDW_STREAM_DISABLED¶
流的禁用状态。退出此状态后,数据端口将被禁用。在此状态之前执行的操作:
通过编程 ChannelEn 寄存器,在备用 bank(当前未使用的 bank)上禁用当前流的所有主设备和从设备端口。
总线和活动流的所有当前配置都被编程到备用 bank(当前未使用的 bank)中。
一旦所有值都编程完毕,总线将启动切换到备用 bank,所有新编程的值将在此生效,并且与当前流关联的端口将被禁用。
所有上述操作成功后,流状态设置为 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 寄存器,解除准备当前流的所有主设备和从设备端口。
通过执行 bank 切换等操作,当前流的有效负载带宽从总线总带宽需求中减少,并计算和应用新参数。
所有上述操作成功后,流状态设置为 SDW_STREAM_DEPREPARED
。
总线实现了用于 DEPREPARED 状态的以下 API,每个流需要调用一次。ALSA/ASoC 没有“去准备”的概念,因此此流状态到 ALSA/ASoC 操作的映射可能是实现特定的。
当支持 INFO_PAUSE 标志时,流状态与 .hw_free() 操作相关联——流在 TRIGGER_STOP 时不会被去准备。
如果其他实现需要通过 SDW_STREAM_PREPARED
状态进行转换,则它们可能在 TRIGGER_STOP 时转换到 SDW_STREAM_DEPREPARED
状态。
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() 回调期间调用,它清除连接到流的所有 DAI 的流指针并释放为流分配的内存。
不支持¶
不支持在两个流之间或跨流使用具有多个通道的单个端口。例如,一个具有 4 个通道的端口不能用于处理 2 个独立的立体声流,即使这在 SoundWire 中理论上是可能的。