* [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support
@ 2026-11-07 22:07 Vijendar Mukunda
2026-11-07 22:07 ` [PATCH 01/11] soundwire: amd: add ACP7B/7E/7F register definitions and revision IDs Vijendar Mukunda
` (10 more replies)
0 siblings, 11 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:07 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
This series adds the first drop of SoundWire manager support for AMD
ACP7B, ACP7E, and ACP7F platforms. These revisions represent a
significant hardware evolution compared to the ACP7.0/7.1/7.2
generation:
- Up to four SoundWire manager instances (ACP_SDW0 through ACP_SDW3),
compared to two in earlier generations.
- Banked registers on the manager side for clock frequency and frame
shape parameters, enabling seamless transitions without bus
re-enumeration.
- Dynamic clock scaling with both fixed frame shape and dynamic frame
shape selection, enabling efficient bandwidth use across a wider
range of bus clock frequencies.
- Extended sample rate and bit-depth support (48/96/192 kHz,
S16/S24/S32) and a broader set of supported bus clock frequencies
compared to ACP7.0/7.1/7.2.
- Up to 22 DAIs per manager with 11 TX and 11 RX data ports and
8 TX and 8 RX DMA channels, compared to fewer ports and DMAs
in earlier generations.
- SSP synchronisation tag propagation per bus transfer, required by
the ACP7B/7E/7F hardware and absent in older revisions.
- Shared per-platform wake enable control across all manager instances
with software serialisation to prevent races between concurrent
managers during clock stop.
- Separate wake and bus-event IRQ paths, giving cleaner interrupt
handling compared to earlier generations.
- Per-revision IO pad pin assignments with hardware-required restore
order on D0 resume after power-off (ACP7B, ACP7E, and ACP7F each
have distinct physical SoundWire clock and data pin mappings).
- Custom bandwidth allocation with dynamic frame-shape selection
across candidate clock frequencies.
The new backend amd_acp7x_manager.c plugs into the hw_ops dispatch
framework introduced by the refactor series. It handles the full
driver lifecycle: IRQ handling and slave status processing, port
configuration, DAI PCM capability registration, host wake
enable/disable, clock-stop suspend and resume, and IO pad management.
Multi-manager synchronisation (GSYNC) is planned for a future series
once the required design work is complete; the existing implementation
is structured to accommodate it incrementally.
Two patches to generic_bandwidth_allocation.c are included: one
fixes the hwidth computation for the skipped block-offset slot
(a correctness fix relevant to any user of the generic algorithm),
and one exports the bandwidth helper functions needed by the new
ACP7x compute_params callback.
The interrupt_enabled guard used by ACP6.3/7.0/7.1/7.2 to prevent
new work from being queued during teardown is mirrored in the ACP7x
enable/disable and IRQ thread paths. The corresponding ASoC-side
patches that guard interrupt_enabled checks in the ACP7B/7E/7F IRQ
handler are not included in this series and will be posted separately.
Dependencies:
This series applies on top of the AMD SoundWire fix series (v5,
25 patches) and the AMD SoundWire refactor series (v3, 11 patches):
https://lore.kernel.org/linux-sound/20261107154659.3358223-1-Vijendar.Mukunda@amd.com/
https://lore.kernel.org/linux-sound/20261107161345.3411491-1-Vijendar.Mukunda@amd.com/
Vijendar Mukunda (11):
soundwire: amd: add ACP7B/7E/7F register definitions and revision IDs
soundwire: amd: add ACP7B/7E/7F hardware backend
soundwire: amd: extend ctl_word_prep to set SSP_TAG for ACP7B/7E/7F
soundwire: amd: add ACP7B/7E/7F DAI PCM capabilities in
register_dais()
soundwire: amd: enable host wake support for ACP7B/7E/7F
soundwire: amd: add ACP7B/7E/7F clock-stop support
soundwire: amd: add IO pad enable/disable for ACP7B/7E/7F D0/D3
sequence
soundwire: generic_bandwidth_allocation: fix hwidth for skipped block
offset
soundwire: generic_bandwidth_allocation: export bandwidth helper
functions
soundwire: amd: add ACP7x generic bandwidth allocation support
soundwire: amd: add interrupt_enabled synchronization for ACP7B/7E/7F
drivers/soundwire/Makefile | 2 +-
drivers/soundwire/amd_acp7x_manager.c | 1163 +++++++++++++++++
drivers/soundwire/amd_init.c | 365 +++++-
drivers/soundwire/amd_init.h | 13 +
drivers/soundwire/amd_manager.h | 297 +++++
drivers/soundwire/amd_manager_common.c | 130 +-
drivers/soundwire/bus.h | 10 +
.../soundwire/generic_bandwidth_allocation.c | 129 +-
include/linux/soundwire/sdw_amd.h | 28 +-
9 files changed, 2116 insertions(+), 21 deletions(-)
create mode 100644 drivers/soundwire/amd_acp7x_manager.c
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 01/11] soundwire: amd: add ACP7B/7E/7F register definitions and revision IDs
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
@ 2026-11-07 22:07 ` Vijendar Mukunda
2026-11-07 22:07 ` [PATCH 02/11] soundwire: amd: add ACP7B/7E/7F hardware backend Vijendar Mukunda
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:07 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
Add ACP7B/7E/7F PCI revision ID constants (ACP7B_PCI_REV_ID,
ACP7E_PCI_REV_ID, ACP7F_PCI_REV_ID), SoundWire instance indices
ACP_SDW2 and ACP_SDW3, and AMD_ACP7X_SDW_MAX_MANAGER_COUNT (4)
for the four-manager topology supported by these revisions.
Add ACP7X_* register offset definitions for manager enable/disable,
interrupt control, IMM command/response, state-change status,
clock frequency control, frame size, bus reset, ping status, wake
enable, external interrupt control, and all TX/RX data port registers
(DP0-DP7 with per-bank sub-port layout).
Add struct sdw_manager_acp7x_dp_reg to describe the per-DAI register
layout for ACP7x data ports, and extend acp_sdw_pdata with mmio_base
for direct per-manager ACP register access without a second ioremap
call.
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
---
drivers/soundwire/amd_manager.h | 289 ++++++++++++++++++++++++++++++
include/linux/soundwire/sdw_amd.h | 8 +
2 files changed, 297 insertions(+)
diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h
index 2e87c79580f4..f58c99ff24d4 100644
--- a/drivers/soundwire/amd_manager.h
+++ b/drivers/soundwire/amd_manager.h
@@ -118,6 +118,276 @@
#define ACP_SW_ERROR_INTR_MASK 0x0003270
#define ACP_SW_PHY_TEST_MODE_DATA_OFF 0x0003274
+/* ACP7B/7E/7F SoundWire manager register offset between successive instances */
+#define ACP7X_SDW_MANAGER_REG_OFFSET 0x2000
+
+/* ACP7B/7E/7F IMM command/response registers (ACP-base relative) */
+#define ACP7X_SW_IMM_CMD_UPPER_WORD 0x005130
+#define ACP7X_SW_IMM_CMD_LOWER_QWORD 0x005134
+#define ACP7X_SW_IMM_RESP_UPPER_WORD 0x005138
+#define ACP7X_SW_IMM_RESP_LOWER_QWORD 0x00513c
+#define ACP7X_SW_IMM_CMD_STS 0x005140
+
+/* ACP7B/7E/7F slave state-change status registers */
+#define ACP7X_SW_STATE_CHANGE_STATUS_0TO7 0x00515c
+#define ACP7X_SW_STATE_CHANGE_STATUS_8TO11 0x005160
+#define ACP7X_SW_STATE_CHANGE_STATUS_MASK_0TO7 0x005164
+#define ACP7X_SW_STATE_CHANGE_STATUS_MASK_8TO11 0x005168
+
+/* ACP7B/7E/7F clock frequency control registers (one per bank) */
+#define ACP7X_SW_CLK_FREQUENCY_CTRL_BANK0 0x00516c
+#define ACP7X_SW_CLK_FREQUENCY_CTRL_BANK1 0x005170
+
+/* ACP7B/7E/7F error interrupt mask */
+#define ACP7X_SW_ERROR_INTR_MASK 0x005174
+
+/* ACP7B/7E/7F manager enable / frame-shape registers */
+#define ACP7X_SW_EN 0x005200
+#define ACP7X_SW_EN_STATUS 0x005204
+#define ACP7X_SW_FRAMESIZE_BANK0 0x005208
+#define ACP7X_SW_FRAMESIZE_BANK1 0x00520c
+
+/* ACP7B/7E/7F clock-stop resume control */
+#define ACP7X_SW_CLK_RESUME_CTRL 0x0057f8
+#define ACP7X_SW_CLK_RESUME_DELAY_CNTR 0x0057fc
+#define ACP7X_SW_BUS_RESET_CTRL 0x005800
+
+/* ACP7B/7E/7F ping status registers */
+#define ACP7X_SW_PING_STATUS_REGISTER_LOW 0x005810
+#define ACP7X_SW_PING_STATUS_REGISTER_HIGH 0x005814
+#define ACP7X_SW_PING_STATUS_CURRENT_BANK_SEL 0x005818
+
+/* ACP7B/7E/7F capabilities register */
+#define ACP7X_SW_GLOBAL_CAPABILITIES 0x004e00
+
+/* ACP7B/7E/7F wake enable */
+#define ACP7X_SW_WAKE_EN 0x0001458
+
+/*
+ * ACP7B/7E/7F external interrupt control registers (ACP-level).
+ * Offset 0x1a0c is a status register on ACP63 and a control register here.
+ */
+#define ACP7X_EXTERNAL_INTR_CNTL 0x0001a04
+#define ACP7X_EXTERNAL_INTR_CNTL1 0x0001a08
+#define ACP7X_EXTERNAL_SW_INTR_CNTL 0x0001a0c
+
+/* ACP7B/7E/7F per-manager interrupt masks (within ACP7X_EXTERNAL_INTR_CNTL) */
+#define ACP7X_SDW0_EXT_INTR_MASK BIT(23)
+#define ACP7X_SDW1_EXT_INTR_MASK BIT(24)
+#define ACP7X_SDW2_EXT_INTR_MASK BIT(25)
+#define ACP7X_SDW3_EXT_INTR_MASK BIT(26)
+
+/* ACP7B/7E/7F host-wake interrupt masks (within ACP7X_EXTERNAL_INTR_CNTL1, 0x1a08) */
+#define ACP7X_SDW0_HOST_WAKE_INTR_MASK BIT(22)
+#define ACP7X_SDW1_HOST_WAKE_INTR_MASK BIT(23)
+#define ACP7X_SDW2_HOST_WAKE_INTR_MASK BIT(24)
+#define ACP7X_SDW3_HOST_WAKE_INTR_MASK BIT(25)
+
+/* ACP7B/7E/7F device state masks within AMD_SDW_DEVICE_STATE register */
+#define ACP7X_SDW0_DEVICE_STATE_MASK GENMASK(1, 0)
+#define ACP7X_SDW1_DEVICE_STATE_MASK GENMASK(3, 2)
+#define ACP7X_SDW2_DEVICE_STATE_MASK GENMASK(5, 4)
+#define ACP7X_SDW3_DEVICE_STATE_MASK GENMASK(7, 6)
+
+/*
+ * ACP7B/7E/7F GLOBAL_CAPABILITIES bitmasks for extracting TX/RX port counts.
+ * The register encodes #TX ports in bits[3:0] and #RX ports in bits[7:4].
+ */
+#define ACP7X_SDW_NO_OF_TX_DATA_PORTS 0x0f
+#define ACP7X_SDW_NO_OF_RX_DATA_PORTS 0xf0
+#define ACP7X_SDW_MAX_DAI 22
+
+/* ACP7B/7E/7F DP register bank stride (bank1 = bank0 + ACP7X_SDW_BANK_REG_OFFSET) */
+#define ACP7X_SDW_BANK_REG_OFFSET 0x18
+
+/* ACP7B/7E/7F field masks for DP registers */
+#define ACP7X_DPN_LCTRL GENMASK(2, 0)
+#define ACP7X_DPN_FRAME_FMT_BLK_PKG_MODE BIT(0)
+#define ACP7X_DPN_FRAME_FMT_BLK_GRP_CTRL GENMASK(2, 1)
+#define ACP7X_DPN_FRAME_FMT_INVERTED BIT(4)
+#define ACP7X_DPN_CH_EN_CHMASK GENMASK(7, 0)
+
+/* ACP7B/7E/7F TX DP frame-format registers (one per data port) */
+#define ACP7X_SW_TX_DP0_FRAME_FORMAT 0x005254
+#define ACP7X_SW_TX_DP1_FRAME_FORMAT 0x005258
+#define ACP7X_SW_TX_DP2_FRAME_FORMAT 0x00525c
+#define ACP7X_SW_TX_DP3_FRAME_FORMAT 0x005260
+#define ACP7X_SW_TX_DP4_FRAME_FORMAT 0x005264
+#define ACP7X_SW_TX_DP5_FRAME_FORMAT 0x005268
+#define ACP7X_SW_TX_DP6_FRAME_FORMAT 0x00526c
+#define ACP7X_SW_TX_DP7_FRAME_FORMAT 0x005270
+
+/* ACP7B/7E/7F TX DP0 sub-port bank-0 transport registers (DP0_0 .. DP0_3) */
+#define ACP7X_SW_TX_DP0_0_SAMPLEINTERVAL_BANK0 0x005280
+#define ACP7X_SW_TX_DP0_0_HCTRL_BANK0 0x005284
+#define ACP7X_SW_TX_DP0_0_HCTRL_OFFSET_BANK0 0x005288
+#define ACP7X_SW_TX_DP0_0_LANE_CTRL_BANK0 0x00528c
+#define ACP7X_SW_TX_DP0_0_CHANNEL_ENABLE_BANK0 0x005290
+#define ACP7X_SW_TX_DP0_0_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005294
+
+#define ACP7X_SW_TX_DP0_1_SAMPLEINTERVAL_BANK0 0x0052b0
+#define ACP7X_SW_TX_DP0_1_HCTRL_BANK0 0x0052b4
+#define ACP7X_SW_TX_DP0_1_HCTRL_OFFSET_BANK0 0x0052b8
+#define ACP7X_SW_TX_DP0_1_LANE_CTRL_BANK0 0x0052bc
+#define ACP7X_SW_TX_DP0_1_CHANNEL_ENABLE_BANK0 0x0052c0
+#define ACP7X_SW_TX_DP0_1_BLOCK_PKGMODE_GRPCTRL_BANK0 0x0052c4
+
+#define ACP7X_SW_TX_DP0_2_SAMPLEINTERVAL_BANK0 0x0052e0
+#define ACP7X_SW_TX_DP0_2_HCTRL_BANK0 0x0052e4
+#define ACP7X_SW_TX_DP0_2_HCTRL_OFFSET_BANK0 0x0052e8
+#define ACP7X_SW_TX_DP0_2_LANE_CTRL_BANK0 0x0052ec
+#define ACP7X_SW_TX_DP0_2_CHANNEL_ENABLE_BANK0 0x0052f0
+#define ACP7X_SW_TX_DP0_2_BLOCK_PKGMODE_GRPCTRL_BANK0 0x0052f4
+
+#define ACP7X_SW_TX_DP0_3_SAMPLEINTERVAL_BANK0 0x005310
+#define ACP7X_SW_TX_DP0_3_HCTRL_BANK0 0x005314
+#define ACP7X_SW_TX_DP0_3_HCTRL_OFFSET_BANK0 0x005318
+#define ACP7X_SW_TX_DP0_3_LANE_CTRL_BANK0 0x00531c
+#define ACP7X_SW_TX_DP0_3_CHANNEL_ENABLE_BANK0 0x005320
+#define ACP7X_SW_TX_DP0_3_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005324
+
+/* ACP7B/7E/7F TX DP1-DP7 bank-0 transport registers */
+#define ACP7X_SW_TX_DP1_SAMPLEINTERVAL_BANK0 0x005370
+#define ACP7X_SW_TX_DP1_HCTRL_BANK0 0x005374
+#define ACP7X_SW_TX_DP1_HCTRL_OFFSET_BANK0 0x005378
+#define ACP7X_SW_TX_DP1_LANE_CTRL_BANK0 0x00537c
+#define ACP7X_SW_TX_DP1_CHANNEL_ENABLE_BANK0 0x005380
+#define ACP7X_SW_TX_DP1_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005384
+
+#define ACP7X_SW_TX_DP2_SAMPLEINTERVAL_BANK0 0x0053a0
+#define ACP7X_SW_TX_DP2_HCTRL_BANK0 0x0053a4
+#define ACP7X_SW_TX_DP2_HCTRL_OFFSET_BANK0 0x0053a8
+#define ACP7X_SW_TX_DP2_LANE_CTRL_BANK0 0x0053ac
+#define ACP7X_SW_TX_DP2_CHANNEL_ENABLE_BANK0 0x0053b0
+#define ACP7X_SW_TX_DP2_BLOCK_PKGMODE_GRPCTRL_BANK0 0x0053b4
+
+#define ACP7X_SW_TX_DP3_SAMPLEINTERVAL_BANK0 0x0053d0
+#define ACP7X_SW_TX_DP3_HCTRL_BANK0 0x0053d4
+#define ACP7X_SW_TX_DP3_HCTRL_OFFSET_BANK0 0x0053d8
+#define ACP7X_SW_TX_DP3_LANE_CTRL_BANK0 0x0053dc
+#define ACP7X_SW_TX_DP3_CHANNEL_ENABLE_BANK0 0x0053e0
+#define ACP7X_SW_TX_DP3_BLOCK_PKGMODE_GRPCTRL_BANK0 0x0053e4
+
+#define ACP7X_SW_TX_DP4_SAMPLEINTERVAL_BANK0 0x005400
+#define ACP7X_SW_TX_DP4_HCTRL_BANK0 0x005404
+#define ACP7X_SW_TX_DP4_HCTRL_OFFSET_BANK0 0x005408
+#define ACP7X_SW_TX_DP4_LANE_CTRL_BANK0 0x00540c
+#define ACP7X_SW_TX_DP4_CHANNEL_ENABLE_BANK0 0x005410
+#define ACP7X_SW_TX_DP4_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005414
+
+#define ACP7X_SW_TX_DP5_SAMPLEINTERVAL_BANK0 0x005430
+#define ACP7X_SW_TX_DP5_HCTRL_BANK0 0x005434
+#define ACP7X_SW_TX_DP5_HCTRL_OFFSET_BANK0 0x005438
+#define ACP7X_SW_TX_DP5_LANE_CTRL_BANK0 0x00543c
+#define ACP7X_SW_TX_DP5_CHANNEL_ENABLE_BANK0 0x005440
+#define ACP7X_SW_TX_DP5_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005444
+
+#define ACP7X_SW_TX_DP6_SAMPLEINTERVAL_BANK0 0x005460
+#define ACP7X_SW_TX_DP6_HCTRL_BANK0 0x005464
+#define ACP7X_SW_TX_DP6_HCTRL_OFFSET_BANK0 0x005468
+#define ACP7X_SW_TX_DP6_LANE_CTRL_BANK0 0x00546c
+#define ACP7X_SW_TX_DP6_CHANNEL_ENABLE_BANK0 0x005470
+#define ACP7X_SW_TX_DP6_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005474
+
+#define ACP7X_SW_TX_DP7_SAMPLEINTERVAL_BANK0 0x005490
+#define ACP7X_SW_TX_DP7_HCTRL_BANK0 0x005494
+#define ACP7X_SW_TX_DP7_HCTRL_OFFSET_BANK0 0x005498
+#define ACP7X_SW_TX_DP7_LANE_CTRL_BANK0 0x00549c
+#define ACP7X_SW_TX_DP7_CHANNEL_ENABLE_BANK0 0x0054a0
+#define ACP7X_SW_TX_DP7_BLOCK_PKGMODE_GRPCTRL_BANK0 0x0054a4
+
+/* ACP7B/7E/7F RX DP frame-format registers */
+#define ACP7X_SW_RX_DP0_FRAME_FORMAT 0x005554
+#define ACP7X_SW_RX_DP1_FRAME_FORMAT 0x005558
+#define ACP7X_SW_RX_DP2_FRAME_FORMAT 0x00555c
+#define ACP7X_SW_RX_DP3_FRAME_FORMAT 0x005560
+#define ACP7X_SW_RX_DP4_FRAME_FORMAT 0x005564
+#define ACP7X_SW_RX_DP5_FRAME_FORMAT 0x005568
+#define ACP7X_SW_RX_DP6_FRAME_FORMAT 0x00556c
+#define ACP7X_SW_RX_DP7_FRAME_FORMAT 0x005570
+
+/* ACP7B/7E/7F RX DP0 sub-port bank-0 transport registers (DP0_0 .. DP0_3) */
+#define ACP7X_SW_RX_DP0_0_SAMPLEINTERVAL_BANK0 0x005580
+#define ACP7X_SW_RX_DP0_0_HCTRL_BANK0 0x005584
+#define ACP7X_SW_RX_DP0_0_HCTRL_OFFSET_BANK0 0x005588
+#define ACP7X_SW_RX_DP0_0_LANE_CTRL_BANK0 0x00558c
+#define ACP7X_SW_RX_DP0_0_CHANNEL_ENABLE_BANK0 0x005590
+#define ACP7X_SW_RX_DP0_0_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005594
+
+#define ACP7X_SW_RX_DP0_1_SAMPLEINTERVAL_BANK0 0x0055b0
+#define ACP7X_SW_RX_DP0_1_HCTRL_BANK0 0x0055b4
+#define ACP7X_SW_RX_DP0_1_HCTRL_OFFSET_BANK0 0x0055b8
+#define ACP7X_SW_RX_DP0_1_LANE_CTRL_BANK0 0x0055bc
+#define ACP7X_SW_RX_DP0_1_CHANNEL_ENABLE_BANK0 0x0055c0
+#define ACP7X_SW_RX_DP0_1_BLOCK_PKGMODE_GRPCTRL_BANK0 0x0055c4
+
+#define ACP7X_SW_RX_DP0_2_SAMPLEINTERVAL_BANK0 0x0055e0
+#define ACP7X_SW_RX_DP0_2_HCTRL_BANK0 0x0055e4
+#define ACP7X_SW_RX_DP0_2_HCTRL_OFFSET_BANK0 0x0055e8
+#define ACP7X_SW_RX_DP0_2_LANE_CTRL_BANK0 0x0055ec
+#define ACP7X_SW_RX_DP0_2_CHANNEL_ENABLE_BANK0 0x0055f0
+#define ACP7X_SW_RX_DP0_2_BLOCK_PKGMODE_GRPCTRL_BANK0 0x0055f4
+
+#define ACP7X_SW_RX_DP0_3_SAMPLEINTERVAL_BANK0 0x005610
+#define ACP7X_SW_RX_DP0_3_HCTRL_BANK0 0x005614
+#define ACP7X_SW_RX_DP0_3_HCTRL_OFFSET_BANK0 0x005618
+#define ACP7X_SW_RX_DP0_3_LANE_CTRL_BANK0 0x00561c
+#define ACP7X_SW_RX_DP0_3_CHANNEL_ENABLE_BANK0 0x005620
+#define ACP7X_SW_RX_DP0_3_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005624
+
+/* ACP7B/7E/7F RX DP1-DP7 bank-0 transport registers */
+#define ACP7X_SW_RX_DP1_SAMPLEINTERVAL_BANK0 0x005670
+#define ACP7X_SW_RX_DP1_HCTRL_BANK0 0x005674
+#define ACP7X_SW_RX_DP1_HCTRL_OFFSET_BANK0 0x005678
+#define ACP7X_SW_RX_DP1_LANE_CTRL_BANK0 0x00567c
+#define ACP7X_SW_RX_DP1_CHANNEL_ENABLE_BANK0 0x005680
+#define ACP7X_SW_RX_DP1_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005684
+
+#define ACP7X_SW_RX_DP2_SAMPLEINTERVAL_BANK0 0x0056a0
+#define ACP7X_SW_RX_DP2_HCTRL_BANK0 0x0056a4
+#define ACP7X_SW_RX_DP2_HCTRL_OFFSET_BANK0 0x0056a8
+#define ACP7X_SW_RX_DP2_LANE_CTRL_BANK0 0x0056ac
+#define ACP7X_SW_RX_DP2_CHANNEL_ENABLE_BANK0 0x0056b0
+#define ACP7X_SW_RX_DP2_BLOCK_PKGMODE_GRPCTRL_BANK0 0x0056b4
+
+#define ACP7X_SW_RX_DP3_SAMPLEINTERVAL_BANK0 0x0056d0
+#define ACP7X_SW_RX_DP3_HCTRL_BANK0 0x0056d4
+#define ACP7X_SW_RX_DP3_HCTRL_OFFSET_BANK0 0x0056d8
+#define ACP7X_SW_RX_DP3_LANE_CTRL_BANK0 0x0056dc
+#define ACP7X_SW_RX_DP3_CHANNEL_ENABLE_BANK0 0x0056e0
+#define ACP7X_SW_RX_DP3_BLOCK_PKGMODE_GRPCTRL_BANK0 0x0056e4
+
+#define ACP7X_SW_RX_DP4_SAMPLEINTERVAL_BANK0 0x005700
+#define ACP7X_SW_RX_DP4_HCTRL_BANK0 0x005704
+#define ACP7X_SW_RX_DP4_HCTRL_OFFSET_BANK0 0x005708
+#define ACP7X_SW_RX_DP4_LANE_CTRL_BANK0 0x00570c
+#define ACP7X_SW_RX_DP4_CHANNEL_ENABLE_BANK0 0x005710
+#define ACP7X_SW_RX_DP4_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005714
+
+#define ACP7X_SW_RX_DP5_SAMPLEINTERVAL_BANK0 0x005730
+#define ACP7X_SW_RX_DP5_HCTRL_BANK0 0x005734
+#define ACP7X_SW_RX_DP5_HCTRL_OFFSET_BANK0 0x005738
+#define ACP7X_SW_RX_DP5_LANE_CTRL_BANK0 0x00573c
+#define ACP7X_SW_RX_DP5_CHANNEL_ENABLE_BANK0 0x005740
+#define ACP7X_SW_RX_DP5_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005744
+
+#define ACP7X_SW_RX_DP6_SAMPLEINTERVAL_BANK0 0x005760
+#define ACP7X_SW_RX_DP6_HCTRL_BANK0 0x005764
+#define ACP7X_SW_RX_DP6_HCTRL_OFFSET_BANK0 0x005768
+#define ACP7X_SW_RX_DP6_LANE_CTRL_BANK0 0x00576c
+#define ACP7X_SW_RX_DP6_CHANNEL_ENABLE_BANK0 0x005770
+#define ACP7X_SW_RX_DP6_BLOCK_PKGMODE_GRPCTRL_BANK0 0x005774
+
+#define ACP7X_SW_RX_DP7_SAMPLEINTERVAL_BANK0 0x005790
+#define ACP7X_SW_RX_DP7_HCTRL_BANK0 0x005794
+#define ACP7X_SW_RX_DP7_HCTRL_OFFSET_BANK0 0x005798
+#define ACP7X_SW_RX_DP7_LANE_CTRL_BANK0 0x00579c
+#define ACP7X_SW_RX_DP7_CHANNEL_ENABLE_BANK0 0x0057a0
+#define ACP7X_SW_RX_DP7_BLOCK_PKGMODE_GRPCTRL_BANK0 0x0057a4
+
+/* ACP7B/7E/7F SSP counter (shared with ACP63 naming but different base) */
+#define ACP7X_SW_SSP_COUNTER 0x005210
+
#define ACP_DELAY_US 10
#define AMD_SDW_TIMEOUT 1000
#define AMD_SDW_DEFAULT_CLK_FREQ 12000000
@@ -196,6 +466,25 @@
#define AMD_SDW_DEVICE_STATE_D0 0
#define AMD_SDW_DEVICE_STATE_D3 3
#define ACP_PME_EN 0x0001400
+#define ACP_SW_48MHZ_CLK_SEL 0x1030
+#define AMD_SDW_MCP_FRAME_SHAPE_COL_MASK GENMASK(2, 0)
+#define AMD_SDW_MCP_FRAME_SHAPE_ROW_MASK GENMASK(7, 3)
+#define AMD_SDW_8TO11_DEV_STATUS 0xffff
+
+/*
+ * ACP7B/7E/7F DP register layout. Frame-format is shared across banks; sample
+ * interval, hctrl, offset, lane-ctrl, channel-enable, and config registers
+ * are bank-0 base addresses (bank-1 = bank-0 + ACP7X_SDW_BANK_REG_OFFSET).
+ */
+struct sdw_manager_acp7x_dp_reg {
+ u32 frame_fmt_reg;
+ u32 sample_int_reg;
+ u32 hctrl_dp_reg;
+ u32 offset_reg;
+ u32 lane_ctrl_reg;
+ u32 ch_en_reg;
+ u32 config_reg;
+};
/* Cross-file prototypes: amd_manager_common.c functions used by amd_acp63_manager.c */
enum sdw_command_response amd_sdw_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg);
diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h
index 64e7cea8bd50..7dfb17603159 100644
--- a/include/linux/soundwire/sdw_amd.h
+++ b/include/linux/soundwire/sdw_amd.h
@@ -26,14 +26,22 @@
#define AMD_SDW_POWER_OFF_MODE 2
#define ACP_SDW0 0
#define ACP_SDW1 1
+#define ACP_SDW2 2
+#define ACP_SDW3 3
/* ACP6.3/7.0/7.1/7.2 all expose two SoundWire manager instances */
#define AMD_ACP63_SDW_MAX_MANAGER_COUNT 2
#define ACP63_PCI_REV_ID 0x63
#define ACP70_PCI_REV_ID 0x70
#define ACP71_PCI_REV_ID 0x71
#define ACP72_PCI_REV_ID 0x72
+#define ACP7B_PCI_REV_ID 0x7b
+#define ACP7E_PCI_REV_ID 0x7e
+#define ACP7F_PCI_REV_ID 0x7f
+/* ACP7B/7E/7F exposes up to four SoundWire manager instances */
+#define AMD_ACP7X_SDW_MAX_MANAGER_COUNT 4
struct acp_sdw_pdata {
+ void __iomem *mmio_base;
u16 instance;
u32 acp_rev;
/* mutex to protect acp common register access */
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 02/11] soundwire: amd: add ACP7B/7E/7F hardware backend
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
2026-11-07 22:07 ` [PATCH 01/11] soundwire: amd: add ACP7B/7E/7F register definitions and revision IDs Vijendar Mukunda
@ 2026-11-07 22:07 ` Vijendar Mukunda
2026-11-07 22:07 ` [PATCH 03/11] soundwire: amd: extend ctl_word_prep to set SSP_TAG for ACP7B/7E/7F Vijendar Mukunda
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:07 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
Add amd_acp7x_manager.c implementing the hardware backend for
ACP7B/7E/7F revisions: IRQ thread, slave status work, port
configuration, DAI PCM capabilities, and hw_ops/port_ops tables.
Wire the new backend into amd_manager_common.c by extending the
per-revision switch/case in amd_sdw_update_slave_status_work(),
amd_sdw_irq_thread(), amd_sdw_hw_init_ops(), and
amd_sdw_manager_probe(). The probe reads ACP7X_SW_GLOBAL_CAPABILITIES
to determine TX/RX port counts and sets the ACP7x-specific mmio base
using the ACP7X_SDW_MANAGER_REG_OFFSET stride.
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
---
drivers/soundwire/Makefile | 2 +-
drivers/soundwire/amd_acp7x_manager.c | 949 +++++++++++++++++++++++++
drivers/soundwire/amd_manager.h | 7 +
drivers/soundwire/amd_manager_common.c | 49 ++
4 files changed, 1006 insertions(+), 1 deletion(-)
create mode 100644 drivers/soundwire/amd_acp7x_manager.c
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile
index 1011599c20e7..11f7aa8b0476 100644
--- a/drivers/soundwire/Makefile
+++ b/drivers/soundwire/Makefile
@@ -20,7 +20,7 @@ soundwire-bus-y += irq.o
endif
#AMD driver
-soundwire-amd-y := amd_init.o amd_manager_common.o amd_acp63_manager.o
+soundwire-amd-y := amd_init.o amd_manager_common.o amd_acp63_manager.o amd_acp7x_manager.o
obj-$(CONFIG_SOUNDWIRE_AMD) += soundwire-amd.o
#Cadence Objs
diff --git a/drivers/soundwire/amd_acp7x_manager.c b/drivers/soundwire/amd_acp7x_manager.c
new file mode 100644
index 000000000000..33fc9ced48b6
--- /dev/null
+++ b/drivers/soundwire/amd_acp7x_manager.c
@@ -0,0 +1,949 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * SoundWire AMD ACP7x (ACP7B/7E/7F) Manager hardware ops
+ *
+ * Copyright 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/pm_runtime.h>
+#include <linux/wait.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "bus.h"
+#include "amd_init.h"
+#include "amd_manager.h"
+
+#define to_amd_sdw(b) container_of(b, struct amd_sdw_manager, bus)
+
+static u32 acp7x_sdw_manager_reg_mask_array[AMD_ACP7X_SDW_MAX_MANAGER_COUNT] = {
+ ACP7X_SDW0_EXT_INTR_MASK,
+ ACP7X_SDW1_EXT_INTR_MASK,
+ ACP7X_SDW2_EXT_INTR_MASK,
+ ACP7X_SDW3_EXT_INTR_MASK,
+};
+
+static struct sdw_manager_acp7x_dp_reg acp7x_sdw_dp_reg[] = {
+ {ACP7X_SW_TX_DP0_FRAME_FORMAT, ACP7X_SW_TX_DP0_0_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_TX_DP0_0_HCTRL_BANK0, ACP7X_SW_TX_DP0_0_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_TX_DP0_0_LANE_CTRL_BANK0, ACP7X_SW_TX_DP0_0_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_TX_DP0_0_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_TX_DP0_FRAME_FORMAT, ACP7X_SW_TX_DP0_1_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_TX_DP0_1_HCTRL_BANK0, ACP7X_SW_TX_DP0_1_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_TX_DP0_1_LANE_CTRL_BANK0, ACP7X_SW_TX_DP0_1_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_TX_DP0_1_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_TX_DP0_FRAME_FORMAT, ACP7X_SW_TX_DP0_2_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_TX_DP0_2_HCTRL_BANK0, ACP7X_SW_TX_DP0_2_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_TX_DP0_2_LANE_CTRL_BANK0, ACP7X_SW_TX_DP0_2_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_TX_DP0_2_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_TX_DP0_FRAME_FORMAT, ACP7X_SW_TX_DP0_3_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_TX_DP0_3_HCTRL_BANK0, ACP7X_SW_TX_DP0_3_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_TX_DP0_3_LANE_CTRL_BANK0, ACP7X_SW_TX_DP0_3_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_TX_DP0_3_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_TX_DP1_FRAME_FORMAT, ACP7X_SW_TX_DP1_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_TX_DP1_HCTRL_BANK0, ACP7X_SW_TX_DP1_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_TX_DP1_LANE_CTRL_BANK0, ACP7X_SW_TX_DP1_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_TX_DP1_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_TX_DP2_FRAME_FORMAT, ACP7X_SW_TX_DP2_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_TX_DP2_HCTRL_BANK0, ACP7X_SW_TX_DP2_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_TX_DP2_LANE_CTRL_BANK0, ACP7X_SW_TX_DP2_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_TX_DP2_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_TX_DP3_FRAME_FORMAT, ACP7X_SW_TX_DP3_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_TX_DP3_HCTRL_BANK0, ACP7X_SW_TX_DP3_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_TX_DP3_LANE_CTRL_BANK0, ACP7X_SW_TX_DP3_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_TX_DP3_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_TX_DP4_FRAME_FORMAT, ACP7X_SW_TX_DP4_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_TX_DP4_HCTRL_BANK0, ACP7X_SW_TX_DP4_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_TX_DP4_LANE_CTRL_BANK0, ACP7X_SW_TX_DP4_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_TX_DP4_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_TX_DP5_FRAME_FORMAT, ACP7X_SW_TX_DP5_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_TX_DP5_HCTRL_BANK0, ACP7X_SW_TX_DP5_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_TX_DP5_LANE_CTRL_BANK0, ACP7X_SW_TX_DP5_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_TX_DP5_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_TX_DP6_FRAME_FORMAT, ACP7X_SW_TX_DP6_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_TX_DP6_HCTRL_BANK0, ACP7X_SW_TX_DP6_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_TX_DP6_LANE_CTRL_BANK0, ACP7X_SW_TX_DP6_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_TX_DP6_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_TX_DP7_FRAME_FORMAT, ACP7X_SW_TX_DP7_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_TX_DP7_HCTRL_BANK0, ACP7X_SW_TX_DP7_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_TX_DP7_LANE_CTRL_BANK0, ACP7X_SW_TX_DP7_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_TX_DP7_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_RX_DP0_FRAME_FORMAT, ACP7X_SW_RX_DP0_0_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_RX_DP0_0_HCTRL_BANK0, ACP7X_SW_RX_DP0_0_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_RX_DP0_0_LANE_CTRL_BANK0, ACP7X_SW_RX_DP0_0_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_RX_DP0_0_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_RX_DP0_FRAME_FORMAT, ACP7X_SW_RX_DP0_1_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_RX_DP0_1_HCTRL_BANK0, ACP7X_SW_RX_DP0_1_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_RX_DP0_1_LANE_CTRL_BANK0, ACP7X_SW_RX_DP0_1_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_RX_DP0_1_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_RX_DP0_FRAME_FORMAT, ACP7X_SW_RX_DP0_2_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_RX_DP0_2_HCTRL_BANK0, ACP7X_SW_RX_DP0_2_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_RX_DP0_2_LANE_CTRL_BANK0, ACP7X_SW_RX_DP0_2_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_RX_DP0_2_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_RX_DP0_FRAME_FORMAT, ACP7X_SW_RX_DP0_3_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_RX_DP0_3_HCTRL_BANK0, ACP7X_SW_RX_DP0_3_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_RX_DP0_3_LANE_CTRL_BANK0, ACP7X_SW_RX_DP0_3_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_RX_DP0_3_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_RX_DP1_FRAME_FORMAT, ACP7X_SW_RX_DP1_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_RX_DP1_HCTRL_BANK0, ACP7X_SW_RX_DP1_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_RX_DP1_LANE_CTRL_BANK0, ACP7X_SW_RX_DP1_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_RX_DP1_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_RX_DP2_FRAME_FORMAT, ACP7X_SW_RX_DP2_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_RX_DP2_HCTRL_BANK0, ACP7X_SW_RX_DP2_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_RX_DP2_LANE_CTRL_BANK0, ACP7X_SW_RX_DP2_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_RX_DP2_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_RX_DP3_FRAME_FORMAT, ACP7X_SW_RX_DP3_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_RX_DP3_HCTRL_BANK0, ACP7X_SW_RX_DP3_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_RX_DP3_LANE_CTRL_BANK0, ACP7X_SW_RX_DP3_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_RX_DP3_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_RX_DP4_FRAME_FORMAT, ACP7X_SW_RX_DP4_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_RX_DP4_HCTRL_BANK0, ACP7X_SW_RX_DP4_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_RX_DP4_LANE_CTRL_BANK0, ACP7X_SW_RX_DP4_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_RX_DP4_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_RX_DP5_FRAME_FORMAT, ACP7X_SW_RX_DP5_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_RX_DP5_HCTRL_BANK0, ACP7X_SW_RX_DP5_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_RX_DP5_LANE_CTRL_BANK0, ACP7X_SW_RX_DP5_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_RX_DP5_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_RX_DP6_FRAME_FORMAT, ACP7X_SW_RX_DP6_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_RX_DP6_HCTRL_BANK0, ACP7X_SW_RX_DP6_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_RX_DP6_LANE_CTRL_BANK0, ACP7X_SW_RX_DP6_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_RX_DP6_BLOCK_PKGMODE_GRPCTRL_BANK0},
+ {ACP7X_SW_RX_DP7_FRAME_FORMAT, ACP7X_SW_RX_DP7_SAMPLEINTERVAL_BANK0,
+ ACP7X_SW_RX_DP7_HCTRL_BANK0, ACP7X_SW_RX_DP7_HCTRL_OFFSET_BANK0,
+ ACP7X_SW_RX_DP7_LANE_CTRL_BANK0, ACP7X_SW_RX_DP7_CHANNEL_ENABLE_BANK0,
+ ACP7X_SW_RX_DP7_BLOCK_PKGMODE_GRPCTRL_BANK0},
+};
+
+static_assert(ARRAY_SIZE(acp7x_sdw_dp_reg) == ACP7X_SDW_MAX_DAI);
+
+/* =========================================================
+ * ACP7.x (ACP7B/7E/7F) hardware-specific implementation
+ * =========================================================
+ */
+
+static int amd_acp7x_sdw_set_clk_sel(struct amd_sdw_manager *amd_manager, u32 freq)
+{
+ u32 clk_sel;
+
+ if (!freq) {
+ dev_err(amd_manager->dev, "Invalid bus clock frequency: %d\n", freq);
+ return -EINVAL;
+ }
+ switch (freq) {
+ case 3072000:
+ case 6144000:
+ case 12288000:
+ case 24576000:
+ mutex_lock(amd_manager->acp_sdw_lock);
+ clk_sel = readl(amd_manager->acp_mmio + ACP_SW_48MHZ_CLK_SEL);
+ clk_sel |= BIT(amd_manager->instance);
+ writel(clk_sel, amd_manager->acp_mmio + ACP_SW_48MHZ_CLK_SEL);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static u32 amd_sdw_set_initial_frame_shape(u32 rows, u32 cols)
+{
+ u32 val = 0;
+ int c;
+ int r;
+
+ r = sdw_find_row_index(rows);
+ c = sdw_find_col_index(cols);
+ val = FIELD_PREP(AMD_SDW_MCP_FRAME_SHAPE_ROW_MASK, r);
+ val |= FIELD_PREP(AMD_SDW_MCP_FRAME_SHAPE_COL_MASK, c);
+ return val;
+}
+
+static int amd_acp7x_sdw_clk_init_ctrl(struct amd_sdw_manager *amd_manager)
+{
+ struct sdw_bus *bus = &amd_manager->bus;
+ struct sdw_master_prop *prop = &bus->prop;
+ u32 val;
+ int divider;
+ int ret;
+
+ dev_dbg(amd_manager->dev, "mclk %d max %d row %d col %d frame_rate:%d\n",
+ prop->mclk_freq, prop->max_clk_freq, prop->default_row,
+ prop->default_col, prop->default_frame_rate);
+
+ if (!prop->default_frame_rate || !prop->default_row) {
+ dev_err(amd_manager->dev, "Default frame_rate %d or row %d is invalid\n",
+ prop->default_frame_rate, prop->default_row);
+ return -EINVAL;
+ }
+ ret = amd_acp7x_sdw_set_clk_sel(amd_manager, prop->max_clk_freq);
+ if (ret)
+ return ret;
+ if (!bus->params.curr_dr_freq) {
+ dev_err(amd_manager->dev, "Invalid current bus frequency\n");
+ return -EINVAL;
+ }
+ divider = (prop->mclk_freq / bus->params.curr_dr_freq);
+ if (divider == 4)
+ divider = 3;
+ writel(divider, amd_manager->mmio + ACP7X_SW_CLK_FREQUENCY_CTRL_BANK0);
+ writel(divider, amd_manager->mmio + ACP7X_SW_CLK_FREQUENCY_CTRL_BANK1);
+
+ /*
+ * Sync rows_index/cols_index and bus params from the already-selected
+ * default_row/col. For dynamic_frame, these were set by compute_params()
+ * in the frequency switch-case; for static frame, they come from firmware.
+ * Both set_frameshape() and pre_bank_switch() rely on these fields.
+ */
+ amd_manager->cols_index = sdw_find_col_index(prop->default_col);
+ amd_manager->rows_index = sdw_find_row_index(prop->default_row);
+ bus->params.col = prop->default_col;
+ bus->params.row = prop->default_row;
+ val = amd_sdw_set_initial_frame_shape(prop->default_row, prop->default_col);
+ writel(val, amd_manager->mmio + ACP7X_SW_FRAMESIZE_BANK0);
+ writel(val, amd_manager->mmio + ACP7X_SW_FRAMESIZE_BANK1);
+ return 0;
+}
+
+static int amd_acp7x_sdw_send_cmd_get_resp(struct amd_sdw_manager *amd_manager,
+ u32 lower_data, u32 upper_data,
+ u64 *response)
+{
+ u32 lower_resp, upper_resp;
+ u32 sts;
+ int ret;
+
+ ret = readl_poll_timeout(amd_manager->mmio + ACP7X_SW_IMM_CMD_STS, sts,
+ !(sts & AMD_SDW_IMM_CMD_BUSY), ACP_DELAY_US, AMD_SDW_TIMEOUT);
+ if (ret) {
+ dev_err(amd_manager->dev, "SDW%x previous cmd status clear failed -CMD BUSY\n",
+ amd_manager->instance);
+ return ret;
+ }
+ if (sts & AMD_SDW_IMM_RES_VALID) {
+ dev_err(amd_manager->dev, "SDW%x manager is in bad state sts:0x%x\n",
+ amd_manager->instance, sts);
+ writel(AMD_SDW_IMM_RES_VALID, amd_manager->mmio + ACP7X_SW_IMM_CMD_STS);
+ }
+ if (!readl(amd_manager->mmio + ACP7X_SW_SSP_COUNTER))
+ writel(AMD_SDW_SSP_COUNTER_VAL, amd_manager->mmio + ACP7X_SW_SSP_COUNTER);
+ writel(upper_data, amd_manager->mmio + ACP7X_SW_IMM_CMD_UPPER_WORD);
+ writel(lower_data, amd_manager->mmio + ACP7X_SW_IMM_CMD_LOWER_QWORD);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP7X_SW_IMM_CMD_STS, sts,
+ (sts & AMD_SDW_IMM_RES_VALID), ACP_DELAY_US, AMD_SDW_TIMEOUT);
+ if (ret) {
+ dev_err(amd_manager->dev, "SDW%x cmd response timeout occurred\n",
+ amd_manager->instance);
+ return ret;
+ }
+ upper_resp = readl(amd_manager->mmio + ACP7X_SW_IMM_RESP_UPPER_WORD);
+ lower_resp = readl(amd_manager->mmio + ACP7X_SW_IMM_RESP_LOWER_QWORD);
+ writel(AMD_SDW_IMM_RES_VALID, amd_manager->mmio + ACP7X_SW_IMM_CMD_STS);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP7X_SW_IMM_CMD_STS, sts,
+ !(sts & AMD_SDW_IMM_RES_VALID), ACP_DELAY_US, AMD_SDW_TIMEOUT);
+ if (ret) {
+ /* Response already latched above; timeout here is non-fatal.
+ * The residual IMM_RES_VALID bit will be cleared by the bad-state
+ * guard on the next invocation, provided IMM_CMD_BUSY is not also stuck.
+ */
+ dev_warn(amd_manager->dev, "SDW%x cmd status retry failed\n",
+ amd_manager->instance);
+ }
+ *response = ((u64)upper_resp << 32) | lower_resp;
+ return 0;
+}
+
+static void amd_acp7x_sdw_read_and_process_ping_status(struct amd_sdw_manager *amd_manager)
+{
+ u64 response;
+ u32 resp_low, resp_high;
+
+ mutex_lock(&amd_manager->bus.msg_lock);
+ resp_low = readl(amd_manager->mmio + ACP7X_SW_PING_STATUS_REGISTER_LOW);
+ resp_high = readl(amd_manager->mmio + ACP7X_SW_PING_STATUS_REGISTER_HIGH);
+ response = (u64)resp_high << 32 | resp_low;
+ amd_manager->ping_slave_stat =
+ FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response) |
+ (FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8);
+ mutex_unlock(&amd_manager->bus.msg_lock);
+ amd_sdw_process_ping_status(response, amd_manager);
+}
+
+static u32 amd_acp7x_sdw_read_ping_status(struct sdw_bus *bus)
+{
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ u64 response;
+ u32 slave_stat;
+ u32 resp_low, resp_high;
+
+ /* Called by sdw_show_ping_status() which holds msg_lock. */
+ lockdep_assert_held(&bus->msg_lock);
+
+ resp_low = readl(amd_manager->mmio + ACP7X_SW_PING_STATUS_REGISTER_LOW);
+ resp_high = readl(amd_manager->mmio + ACP7X_SW_PING_STATUS_REGISTER_HIGH);
+ response = (u64)resp_high << 32 | resp_low;
+ slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response);
+ slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8;
+ dev_dbg(amd_manager->dev, "slave_stat:0x%x\n", slave_stat);
+ return slave_stat;
+}
+
+static int amd_acp7x_init_sdw_manager(struct amd_sdw_manager *amd_manager)
+{
+ u32 val;
+ int ret;
+
+ writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP7X_SW_EN);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP7X_SW_EN_STATUS, val, val, ACP_DELAY_US,
+ AMD_SDW_TIMEOUT);
+ if (ret)
+ return ret;
+ writel(AMD_SDW_BUS_RESET_REQ, amd_manager->mmio + ACP7X_SW_BUS_RESET_CTRL);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP7X_SW_BUS_RESET_CTRL, val,
+ (val & AMD_SDW_BUS_RESET_DONE), ACP_DELAY_US, AMD_SDW_TIMEOUT);
+ if (ret)
+ return ret;
+ writel(AMD_SDW_BUS_RESET_CLEAR_REQ, amd_manager->mmio + ACP7X_SW_BUS_RESET_CTRL);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP7X_SW_BUS_RESET_CTRL, val, !val,
+ ACP_DELAY_US, AMD_SDW_TIMEOUT);
+ if (ret) {
+ dev_err(amd_manager->dev, "Failed to reset SoundWire manager instance%d\n",
+ amd_manager->instance);
+ return ret;
+ }
+ writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP7X_SW_EN);
+ return readl_poll_timeout(amd_manager->mmio + ACP7X_SW_EN_STATUS, val, !val,
+ ACP_DELAY_US, AMD_SDW_TIMEOUT);
+}
+
+static int amd_acp7x_enable_sdw_manager(struct amd_sdw_manager *amd_manager)
+{
+ u32 val;
+
+ writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP7X_SW_EN);
+ return readl_poll_timeout(amd_manager->mmio + ACP7X_SW_EN_STATUS, val, val, ACP_DELAY_US,
+ AMD_SDW_TIMEOUT);
+}
+
+static int amd_acp7x_disable_sdw_manager(struct amd_sdw_manager *amd_manager)
+{
+ u32 val;
+
+ writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP7X_SW_EN);
+ return readl_poll_timeout(amd_manager->mmio + ACP7X_SW_EN_STATUS, val, !val, ACP_DELAY_US,
+ AMD_SDW_TIMEOUT);
+}
+
+static void amd_acp7x_enable_sdw_interrupts(struct amd_sdw_manager *amd_manager)
+{
+ u32 val, acp_ext_intr_cntl, sdw_intr_cntl;
+
+ mutex_lock(amd_manager->acp_sdw_lock);
+ val = acp7x_sdw_manager_reg_mask_array[amd_manager->instance];
+ acp_ext_intr_cntl = readl(amd_manager->acp_mmio + ACP7X_EXTERNAL_INTR_CNTL);
+ acp_ext_intr_cntl |= val;
+ writel(acp_ext_intr_cntl, amd_manager->acp_mmio + ACP7X_EXTERNAL_INTR_CNTL);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+
+ /* This interrupt control register is private to this manager instance. */
+ sdw_intr_cntl = readl(amd_manager->acp_mmio +
+ ACP7X_EXTERNAL_SW_INTR_CNTL + (amd_manager->instance * 4));
+ sdw_intr_cntl |= BIT(16);
+ writel(sdw_intr_cntl, amd_manager->acp_mmio +
+ ACP7X_EXTERNAL_SW_INTR_CNTL + (amd_manager->instance * 4));
+ writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_MASK_0TO7);
+ writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+ writel(AMD_SDW_IRQ_ERROR_MASK, amd_manager->mmio + ACP7X_SW_ERROR_INTR_MASK);
+}
+
+static void amd_acp7x_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager)
+{
+ u32 val, acp_ext_intr_cntl, sdw_intr_cntl;
+
+ mutex_lock(amd_manager->acp_sdw_lock);
+ val = acp7x_sdw_manager_reg_mask_array[amd_manager->instance];
+ acp_ext_intr_cntl = readl(amd_manager->acp_mmio + ACP7X_EXTERNAL_INTR_CNTL);
+ acp_ext_intr_cntl &= ~val;
+ writel(acp_ext_intr_cntl, amd_manager->acp_mmio + ACP7X_EXTERNAL_INTR_CNTL);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+
+ /* This interrupt control register is private to this manager instance. */
+ sdw_intr_cntl = readl(amd_manager->acp_mmio +
+ ACP7X_EXTERNAL_SW_INTR_CNTL + (amd_manager->instance * 4));
+ sdw_intr_cntl &= ~BIT(16);
+ writel(sdw_intr_cntl, amd_manager->acp_mmio +
+ ACP7X_EXTERNAL_SW_INTR_CNTL + (amd_manager->instance * 4));
+ writel(0x00, amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_MASK_0TO7);
+ writel(0x00, amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+ writel(0x00, amd_manager->mmio + ACP7X_SW_ERROR_INTR_MASK);
+}
+
+static void amd_acp7x_sdw_set_frameshape(struct amd_sdw_manager *amd_manager)
+{
+ u32 frame_size;
+
+ frame_size = (amd_manager->rows_index << 3) | amd_manager->cols_index;
+ writel(frame_size, amd_manager->mmio + ACP7X_SW_FRAMESIZE_BANK0);
+ writel(frame_size, amd_manager->mmio + ACP7X_SW_FRAMESIZE_BANK1);
+}
+
+static void amd_acp7x_sdw_wake_enable(struct amd_sdw_manager *amd_manager, bool enable)
+{
+ u32 wake_ctrl;
+
+ wake_ctrl = readl(amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+ if (enable)
+ wake_ctrl |= AMD_SDW_WAKE_INTR_MASK;
+ else
+ wake_ctrl &= ~AMD_SDW_WAKE_INTR_MASK;
+ writel(wake_ctrl, amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+}
+
+static int amd_acp7x_sdw_host_wake_enable(struct amd_sdw_manager *amd_manager, bool enable)
+{
+ u32 intr_cntl1;
+ u32 sdw_host_wake_irq_mask;
+
+ if (!amd_manager->wake_en_mask)
+ return 0;
+ switch (amd_manager->instance) {
+ case ACP_SDW0:
+ sdw_host_wake_irq_mask = ACP7X_SDW0_HOST_WAKE_INTR_MASK;
+ break;
+ case ACP_SDW1:
+ sdw_host_wake_irq_mask = ACP7X_SDW1_HOST_WAKE_INTR_MASK;
+ break;
+ case ACP_SDW2:
+ sdw_host_wake_irq_mask = ACP7X_SDW2_HOST_WAKE_INTR_MASK;
+ break;
+ case ACP_SDW3:
+ sdw_host_wake_irq_mask = ACP7X_SDW3_HOST_WAKE_INTR_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+ mutex_lock(amd_manager->acp_sdw_lock);
+ intr_cntl1 = readl(amd_manager->acp_mmio + ACP7X_EXTERNAL_INTR_CNTL1);
+ if (enable)
+ intr_cntl1 |= sdw_host_wake_irq_mask;
+ else
+ intr_cntl1 &= ~sdw_host_wake_irq_mask;
+ writel(intr_cntl1, amd_manager->acp_mmio + ACP7X_EXTERNAL_INTR_CNTL1);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+ return 0;
+}
+
+static int amd_acp7x_sdw_set_device_state(struct amd_sdw_manager *amd_manager,
+ u32 target_device_state)
+{
+ u32 sdw_dev_state;
+
+ mutex_lock(amd_manager->acp_sdw_lock);
+ sdw_dev_state = readl(amd_manager->acp_mmio + AMD_SDW_DEVICE_STATE);
+ dev_dbg(amd_manager->dev, "AMD_SDW_DEVICE_STATE:0x%x\n", sdw_dev_state);
+ switch (amd_manager->instance) {
+ case ACP_SDW0:
+ u32p_replace_bits(&sdw_dev_state, target_device_state,
+ ACP7X_SDW0_DEVICE_STATE_MASK);
+ break;
+ case ACP_SDW1:
+ u32p_replace_bits(&sdw_dev_state, target_device_state,
+ ACP7X_SDW1_DEVICE_STATE_MASK);
+ break;
+ case ACP_SDW2:
+ u32p_replace_bits(&sdw_dev_state, target_device_state,
+ ACP7X_SDW2_DEVICE_STATE_MASK);
+ break;
+ case ACP_SDW3:
+ u32p_replace_bits(&sdw_dev_state, target_device_state,
+ ACP7X_SDW3_DEVICE_STATE_MASK);
+ break;
+ default:
+ mutex_unlock(amd_manager->acp_sdw_lock);
+ return -EINVAL;
+ }
+ writel(sdw_dev_state, amd_manager->acp_mmio + AMD_SDW_DEVICE_STATE);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+ return 0;
+}
+
+void amd_acp7x_sdw_update_slave_status_work(struct amd_sdw_manager *amd_manager)
+{
+ int retry_count = 0;
+
+ if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
+ writel(0, amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_MASK_0TO7);
+ writel(0, amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+ }
+
+update_status:
+ sdw_handle_slave_status(&amd_manager->bus, amd_manager->status);
+ /*
+ * During the peripheral enumeration sequence, the SoundWire manager interrupts
+ * are masked. Once the device number programming is done for all peripherals,
+ * interrupts will be unmasked. Read the peripheral device status from ping command
+ * and process the response. This sequence will ensure all peripheral devices enumerated
+ * and initialized properly.
+ */
+ if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
+ if (retry_count++ < SDW_MAX_DEVICES) {
+ writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
+ ACP7X_SW_STATE_CHANGE_STATUS_MASK_0TO7);
+ writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio +
+ ACP7X_SW_STATE_CHANGE_STATUS_MASK_8TO11);
+ acp_sdw_read_and_process_ping_status(amd_manager);
+ goto update_status;
+ } else {
+ dev_err_ratelimited(amd_manager->dev,
+ "Device0 detected after %d iterations\n",
+ retry_count);
+ }
+ }
+}
+
+static void amd_acp7x_sdw_process_wake_event(struct amd_sdw_manager *amd_manager)
+{
+ u32 wake_en, wake_en_mask;
+
+ pm_request_resume(amd_manager->dev);
+ switch (amd_manager->instance) {
+ case ACP_SDW0:
+ wake_en_mask = BIT(ACP_SDW0);
+ break;
+ case ACP_SDW1:
+ wake_en_mask = BIT(ACP_SDW1);
+ break;
+ case ACP_SDW2:
+ wake_en_mask = BIT(ACP_SDW2);
+ break;
+ case ACP_SDW3:
+ wake_en_mask = BIT(ACP_SDW3);
+ break;
+ default:
+ return;
+ }
+ mutex_lock(amd_manager->acp_sdw_lock);
+ wake_en = readl(amd_manager->acp_mmio + ACP7X_SW_WAKE_EN);
+ wake_en &= ~wake_en_mask;
+ writel(wake_en, amd_manager->acp_mmio + ACP7X_SW_WAKE_EN);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+}
+
+void amd_acp7x_sdw_irq_thread(struct amd_sdw_manager *amd_manager)
+{
+ u32 status_change_8to11;
+ u32 status_change_0to7;
+
+ status_change_8to11 = readl(amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_8TO11);
+ status_change_0to7 = readl(amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_0TO7);
+ if (!status_change_0to7 && !status_change_8to11)
+ return;
+
+ /*
+ * Clear both hardware status registers immediately after reading,
+ * before any processing. New events that arrive during processing
+ * are captured in the next IRQ cycle rather than being missed in
+ * the read-to-clear window. All subsequent logic works entirely
+ * from the local copies.
+ * All bits in both STATUS_8TO11 and STATUS_0TO7 are write-zero-to-clear
+ * (W0C) per the hardware register specification.
+ */
+ writel(0x00, amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_8TO11);
+ writel(0x00, amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_0TO7);
+
+ if (status_change_8to11 & AMD_SDW_WAKE_STAT_MASK) {
+ amd_acp7x_sdw_process_wake_event(amd_manager);
+ /*
+ * Strip the wake bit from the local copy and continue to
+ * process any device-state-change in STATUS_0TO7 that arrived
+ * simultaneously (e.g. slave attaching during POWER_OFF_MODE
+ * resume while the slave's SDCA interrupt keeps wake asserted).
+ */
+ status_change_8to11 &= ~AMD_SDW_WAKE_STAT_MASK;
+ if (!status_change_8to11 && !status_change_0to7)
+ return;
+ }
+ /*
+ * PREQ requires the SoundWire bus clock to be running and cannot
+ * be asserted while the manager is in POWER_OFF_MODE D3. When WAKE
+ * and PREQ arrive simultaneously the manager is in clock-stop mode
+ * (D0 with bus clock stopped), where hardware registers are
+ * accessible and reading PING status is valid.
+ */
+ if (status_change_8to11 & AMD_SDW_PREQ_INTR_STAT)
+ amd_acp7x_sdw_read_and_process_ping_status(amd_manager);
+ else if (status_change_0to7 || (status_change_8to11 & AMD_SDW_8TO11_DEV_STATUS))
+ amd_sdw_update_slave_status(status_change_0to7, status_change_8to11, amd_manager);
+ if ((status_change_8to11 & AMD_SDW_PREQ_INTR_STAT) ||
+ (status_change_8to11 & AMD_SDW_8TO11_DEV_STATUS) || status_change_0to7)
+ schedule_work(&amd_manager->amd_sdw_work);
+}
+
+static int amd_acp7x_sdw_clock_stop_exit(struct amd_sdw_manager *amd_manager)
+{
+ u32 val;
+ int ret;
+
+ if (amd_manager->clk_stopped) {
+ val = readl(amd_manager->mmio + ACP7X_SW_CLK_RESUME_CTRL);
+ val |= AMD_SDW_CLK_RESUME_REQ;
+ writel(val, amd_manager->mmio + ACP7X_SW_CLK_RESUME_CTRL);
+ ret = readl_poll_timeout(amd_manager->mmio + ACP7X_SW_CLK_RESUME_CTRL, val,
+ (val & AMD_SDW_CLK_RESUME_DONE), ACP_DELAY_US,
+ AMD_SDW_TIMEOUT);
+ if (ret) {
+ dev_err(amd_manager->dev, "SDW%d clock stop exit failed: %d\n",
+ amd_manager->instance, ret);
+ return ret;
+ }
+
+ writel(0, amd_manager->mmio + ACP7X_SW_CLK_RESUME_CTRL);
+ ret = sdw_bus_exit_clk_stop(&amd_manager->bus);
+ if (ret < 0) {
+ dev_err(amd_manager->dev, "bus failed to exit clock stop %d\n", ret);
+ return ret;
+ }
+ amd_manager->clk_stopped = false;
+ }
+ return 0;
+}
+
+static int amd_acp7x_sdw_suspend(struct amd_sdw_manager *amd_manager)
+{
+ struct sdw_bus *bus = &amd_manager->bus;
+ int ret;
+
+ if (bus->prop.hw_disabled) {
+ dev_dbg(bus->dev, "SoundWire manager %d is disabled, ignoring\n", bus->link_id);
+ return 0;
+ }
+ if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
+ acp_disable_sdw_interrupts(amd_manager);
+ cancel_work_sync(&amd_manager->amd_sdw_irq_thread);
+ cancel_work_sync(&amd_manager->amd_sdw_work);
+ acp_sdw_wake_enable(amd_manager, false);
+ ret = amd_sdw_host_wake_enable_by_rev(amd_manager, false);
+ if (ret)
+ return ret;
+ ret = amd_sdw_clock_stop(amd_manager);
+ if (ret)
+ return ret;
+ acp_enable_sdw_interrupts(amd_manager);
+ } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
+ acp_disable_sdw_interrupts(amd_manager);
+ cancel_work_sync(&amd_manager->amd_sdw_irq_thread);
+ cancel_work_sync(&amd_manager->amd_sdw_work);
+ acp_sdw_wake_enable(amd_manager, false);
+ ret = amd_sdw_host_wake_enable_by_rev(amd_manager, false);
+ if (ret)
+ return ret;
+ ret = amd_sdw_clock_stop(amd_manager);
+ if (ret)
+ return ret;
+ ret = acp_disable_sdw_manager(amd_manager);
+ if (ret)
+ return ret;
+ }
+ return acp_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D3);
+}
+
+static int amd_acp7x_sdw_suspend_runtime(struct amd_sdw_manager *amd_manager)
+{
+ struct sdw_bus *bus = &amd_manager->bus;
+ int ret;
+
+ if (bus->prop.hw_disabled) {
+ dev_dbg(bus->dev, "SoundWire manager %d is disabled, ignoring\n", bus->link_id);
+ return 0;
+ }
+ if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
+ acp_disable_sdw_interrupts(amd_manager);
+ cancel_work_sync(&amd_manager->amd_sdw_irq_thread);
+ cancel_work_sync(&amd_manager->amd_sdw_work);
+ acp_sdw_wake_enable(amd_manager, true);
+ ret = amd_sdw_host_wake_enable_by_rev(amd_manager, true);
+ if (ret)
+ return ret;
+ ret = amd_sdw_clock_stop(amd_manager);
+ if (ret)
+ return ret;
+ acp_enable_sdw_interrupts(amd_manager);
+ } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
+ acp_disable_sdw_interrupts(amd_manager);
+ cancel_work_sync(&amd_manager->amd_sdw_irq_thread);
+ cancel_work_sync(&amd_manager->amd_sdw_work);
+ acp_sdw_wake_enable(amd_manager, true);
+ ret = amd_sdw_host_wake_enable_by_rev(amd_manager, true);
+ if (ret)
+ return ret;
+ ret = amd_sdw_clock_stop(amd_manager);
+ if (ret)
+ return ret;
+ ret = acp_disable_sdw_manager(amd_manager);
+ if (ret)
+ return ret;
+ }
+ ret = acp_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D3);
+ if (ret)
+ return ret;
+ if (amd_manager->wake_en_mask) {
+ u32 val = readl(amd_manager->acp_mmio + ACP_PME_EN);
+
+ if (!val) {
+ writel(1, amd_manager->acp_mmio + ACP_PME_EN);
+ dev_dbg(amd_manager->dev, "ACP_PME_EN enabled\n");
+ }
+ }
+ return 0;
+}
+
+static int amd_acp7x_sdw_resume_runtime(struct amd_sdw_manager *amd_manager)
+{
+ struct sdw_bus *bus = &amd_manager->bus;
+ u32 wake_en;
+ int ret;
+
+ if (bus->prop.hw_disabled) {
+ dev_dbg(bus->dev, "SoundWire manager %d is disabled, ignoring\n", bus->link_id);
+ return 0;
+ }
+ if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
+ ret = amd_sdw_host_wake_enable_by_rev(amd_manager, false);
+ if (ret)
+ return ret;
+ ret = acp_sdw_clock_stop_exit(amd_manager);
+ if (ret)
+ return ret;
+ } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
+ mutex_lock(amd_manager->acp_sdw_lock);
+ wake_en = readl(amd_manager->acp_mmio + ACP7X_SW_WAKE_EN);
+ wake_en &= ~BIT(amd_manager->instance);
+ writel(wake_en, amd_manager->acp_mmio + ACP7X_SW_WAKE_EN);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+ ret = amd_sdw_host_wake_enable_by_rev(amd_manager, false);
+ if (ret)
+ return ret;
+ sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
+ ret = acp_sdw_clk_init_ctrl(amd_manager);
+ if (ret)
+ return ret;
+ ret = acp_init_sdw_manager(amd_manager);
+ if (ret)
+ return ret;
+ acp_enable_sdw_interrupts(amd_manager);
+ ret = acp_enable_sdw_manager(amd_manager);
+ if (ret)
+ return ret;
+ acp_sdw_set_frameshape(amd_manager);
+ }
+ return acp_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D0);
+}
+
+static int amd_acp7x_sdw_bus_conf(struct sdw_bus *bus, struct sdw_bus_params *params)
+{
+ struct sdw_master_prop *prop = &bus->prop;
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ int clkctrl_reg;
+ int divider;
+
+ if (!params->curr_dr_freq) {
+ dev_err(amd_manager->dev, "NULL curr_dr_freq\n");
+ return -EINVAL;
+ }
+ divider = (prop->mclk_freq / params->curr_dr_freq);
+ if (divider == 4)
+ divider = 3;
+ if (params->next_bank)
+ clkctrl_reg = ACP7X_SW_CLK_FREQUENCY_CTRL_BANK1;
+ else
+ clkctrl_reg = ACP7X_SW_CLK_FREQUENCY_CTRL_BANK0;
+ writel(divider, amd_manager->mmio + clkctrl_reg);
+ return 0;
+}
+
+static int amd_acp7x_sdw_port_params(struct sdw_bus *bus, struct sdw_port_params *p_params,
+ unsigned int bank)
+{
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ u32 frame_fmt_reg, dpn_frame_fmt;
+
+ if (p_params->num >= ACP7X_SDW_MAX_DAI) {
+ dev_err(amd_manager->dev, "port_num %d out of range\n", p_params->num);
+ return -EINVAL;
+ }
+ frame_fmt_reg = acp7x_sdw_dp_reg[p_params->num].frame_fmt_reg;
+ dpn_frame_fmt = readl(amd_manager->mmio + frame_fmt_reg);
+ u32p_replace_bits(&dpn_frame_fmt, p_params->flow_mode, AMD_DPN_FRAME_FMT_PFM);
+ u32p_replace_bits(&dpn_frame_fmt, p_params->data_mode, AMD_DPN_FRAME_FMT_PDM);
+ u32p_replace_bits(&dpn_frame_fmt, p_params->bps - 1, AMD_DPN_FRAME_FMT_WORD_LEN);
+ writel(dpn_frame_fmt, amd_manager->mmio + frame_fmt_reg);
+ return 0;
+}
+
+static int amd_acp7x_sdw_transport_params(struct sdw_bus *bus,
+ struct sdw_transport_params *params,
+ enum sdw_reg_bank bank)
+{
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ u32 dpn_frame_fmt;
+ u32 dpn_config;
+ u32 dpn_sampleinterval;
+ u32 dpn_hctrl;
+ u32 dpn_offsetctrl;
+ u32 dpn_lanectrl;
+ u32 frame_fmt_reg, sample_int_reg, hctrl_dp_reg;
+ u32 offset_reg, lane_ctrl_reg, block_config_reg;
+ u32 val;
+ u32 reg_offset;
+
+ if (params->port_num >= ACP7X_SDW_MAX_DAI) {
+ dev_err(amd_manager->dev, "port_num %d out of range\n", params->port_num);
+ return -EINVAL;
+ }
+ frame_fmt_reg = acp7x_sdw_dp_reg[params->port_num].frame_fmt_reg;
+ reg_offset = bank ? ACP7X_SDW_BANK_REG_OFFSET : 0;
+ sample_int_reg = acp7x_sdw_dp_reg[params->port_num].sample_int_reg + reg_offset;
+ hctrl_dp_reg = acp7x_sdw_dp_reg[params->port_num].hctrl_dp_reg + reg_offset;
+ offset_reg = acp7x_sdw_dp_reg[params->port_num].offset_reg + reg_offset;
+ lane_ctrl_reg = acp7x_sdw_dp_reg[params->port_num].lane_ctrl_reg + reg_offset;
+ block_config_reg = acp7x_sdw_dp_reg[params->port_num].config_reg + reg_offset;
+
+ val = readl(amd_manager->mmio + ACP7X_SW_SSP_COUNTER);
+ if (!val) {
+ writel(AMD_SDW_SSP_COUNTER_VAL, amd_manager->mmio + ACP7X_SW_SSP_COUNTER);
+ val = readl(amd_manager->mmio + ACP7X_SW_SSP_COUNTER);
+ }
+ dpn_frame_fmt = readl(amd_manager->mmio + frame_fmt_reg);
+ u32p_replace_bits(&dpn_frame_fmt, 0, ACP7X_DPN_FRAME_FMT_INVERTED);
+ u32p_replace_bits(&dpn_frame_fmt, SDW_STREAM_PCM, AMD_DPN_FRAME_FMT_PCM_OR_PDM);
+ writel(dpn_frame_fmt, amd_manager->mmio + frame_fmt_reg);
+
+ dpn_config = readl(amd_manager->mmio + block_config_reg);
+ u32p_replace_bits(&dpn_config, params->blk_pkg_mode, ACP7X_DPN_FRAME_FMT_BLK_PKG_MODE);
+ u32p_replace_bits(&dpn_config, params->blk_grp_ctrl, ACP7X_DPN_FRAME_FMT_BLK_GRP_CTRL);
+ writel(dpn_config, amd_manager->mmio + block_config_reg);
+
+ dpn_sampleinterval = params->sample_interval - 1;
+ writel(dpn_sampleinterval, amd_manager->mmio + sample_int_reg);
+
+ dpn_hctrl = FIELD_PREP(AMD_DPN_HCTRL_HSTOP, params->hstop);
+ dpn_hctrl |= FIELD_PREP(AMD_DPN_HCTRL_HSTART, params->hstart);
+ writel(dpn_hctrl, amd_manager->mmio + hctrl_dp_reg);
+
+ dpn_offsetctrl = FIELD_PREP(AMD_DPN_OFFSET_CTRL_1, params->offset1);
+ dpn_offsetctrl |= FIELD_PREP(AMD_DPN_OFFSET_CTRL_2, params->offset2);
+ writel(dpn_offsetctrl, amd_manager->mmio + offset_reg);
+
+ dpn_lanectrl = readl(amd_manager->mmio + lane_ctrl_reg);
+ u32p_replace_bits(&dpn_lanectrl, params->lane_ctrl, ACP7X_DPN_LCTRL);
+ writel(dpn_lanectrl, amd_manager->mmio + lane_ctrl_reg);
+ return 0;
+}
+
+static int amd_acp7x_sdw_port_enable(struct sdw_bus *bus,
+ struct sdw_enable_ch *enable_ch,
+ unsigned int bank)
+{
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ u32 dpn_ch_enable;
+ u32 ch_en_reg;
+ u32 reg_offset;
+
+ if (enable_ch->port_num >= ACP7X_SDW_MAX_DAI) {
+ dev_err(amd_manager->dev, "port_num %d out of range\n", enable_ch->port_num);
+ return -EINVAL;
+ }
+ reg_offset = bank ? ACP7X_SDW_BANK_REG_OFFSET : 0;
+ ch_en_reg = acp7x_sdw_dp_reg[enable_ch->port_num].ch_en_reg + reg_offset;
+ dpn_ch_enable = readl(amd_manager->mmio + ch_en_reg);
+ u32p_replace_bits(&dpn_ch_enable, enable_ch->ch_mask, ACP7X_DPN_CH_EN_CHMASK);
+ if (enable_ch->enable)
+ writel(dpn_ch_enable, amd_manager->mmio + ch_en_reg);
+ else
+ writel(0, amd_manager->mmio + ch_en_reg);
+ return 0;
+}
+
+static int amd_acp7x_sdw_pre_bank_switch(struct sdw_bus *bus)
+{
+ struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
+ u32 frame_size;
+ u32 rows_index, cols_index;
+
+ rows_index = sdw_find_row_index(bus->params.row);
+ cols_index = sdw_find_col_index(bus->params.col);
+ frame_size = (rows_index << 3) | cols_index;
+ if (amd_manager->bus.params.next_bank)
+ writel(frame_size, amd_manager->mmio + ACP7X_SW_FRAMESIZE_BANK1);
+ else
+ writel(frame_size, amd_manager->mmio + ACP7X_SW_FRAMESIZE_BANK0);
+ return 0;
+}
+
+static int amd_acp7x_sdw_post_bank_switch(struct sdw_bus *bus)
+{
+ u32 frame_size, frame_rate;
+ u32 us_delay;
+
+ frame_size = bus->params.row * bus->params.col;
+ if (!frame_size || !bus->params.curr_dr_freq)
+ return 0;
+
+ frame_rate = bus->params.curr_dr_freq / frame_size;
+ if (!frame_rate)
+ return 0;
+
+ /* Wait for one frame period after switching banks. */
+ us_delay = 1000000 / frame_rate;
+ udelay(us_delay);
+ return 0;
+}
+
+const struct sdw_master_port_ops amd_acp7x_sdw_port_ops = {
+ .dpn_set_port_params = amd_acp7x_sdw_port_params,
+ .dpn_set_port_transport_params = amd_acp7x_sdw_transport_params,
+ .dpn_port_enable_ch = amd_acp7x_sdw_port_enable,
+};
+
+const struct sdw_master_ops amd_acp7x_sdw_ops = {
+ .read_prop = amd_prop_read,
+ .set_bus_conf = amd_acp7x_sdw_bus_conf,
+ .xfer_msg = amd_sdw_xfer_msg,
+ .read_ping_status = amd_acp7x_sdw_read_ping_status,
+ .pre_bank_switch = amd_acp7x_sdw_pre_bank_switch,
+ .post_bank_switch = amd_acp7x_sdw_post_bank_switch,
+};
+
+static const struct amd_sdw_hw_ops amd_acp7x_hw_ops = {
+ .init_sdw_manager = amd_acp7x_init_sdw_manager,
+ .enable_sdw_manager = amd_acp7x_enable_sdw_manager,
+ .disable_sdw_manager = amd_acp7x_disable_sdw_manager,
+ .enable_sdw_interrupts = amd_acp7x_enable_sdw_interrupts,
+ .disable_sdw_interrupts = amd_acp7x_disable_sdw_interrupts,
+ .set_frameshape = amd_acp7x_sdw_set_frameshape,
+ .send_cmd_get_resp = amd_acp7x_sdw_send_cmd_get_resp,
+ .wake_enable = amd_acp7x_sdw_wake_enable,
+ .host_wake_enable = amd_acp7x_sdw_host_wake_enable,
+ .set_device_state = amd_acp7x_sdw_set_device_state,
+ .read_and_process_ping_status = amd_acp7x_sdw_read_and_process_ping_status,
+ .process_wake_event = amd_acp7x_sdw_process_wake_event,
+ .clk_init_ctrl = amd_acp7x_sdw_clk_init_ctrl,
+ .clock_stop_exit = amd_acp7x_sdw_clock_stop_exit,
+ .suspend = amd_acp7x_sdw_suspend,
+ .suspend_runtime = amd_acp7x_sdw_suspend_runtime,
+ .resume_runtime = amd_acp7x_sdw_resume_runtime,
+};
+
+int amd_sdw_hw_init_ops_acp7x(struct amd_sdw_manager *amd_manager)
+{
+ amd_manager->hw_ops = &amd_acp7x_hw_ops;
+ return 0;
+}
diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h
index f58c99ff24d4..f0d3c5684744 100644
--- a/drivers/soundwire/amd_manager.h
+++ b/drivers/soundwire/amd_manager.h
@@ -504,6 +504,13 @@ extern const struct sdw_master_ops amd_acp63_sdw_ops;
void amd_acp63_sdw_irq_thread(struct amd_sdw_manager *amd_manager);
void amd_acp63_sdw_update_slave_status_work(struct amd_sdw_manager *amd_manager);
+/* Cross-file prototypes: amd_acp7x_manager.c symbols used by amd_manager_common.c */
+void amd_acp7x_sdw_irq_thread(struct amd_sdw_manager *amd_manager);
+void amd_acp7x_sdw_update_slave_status_work(struct amd_sdw_manager *amd_manager);
+int amd_sdw_hw_init_ops_acp7x(struct amd_sdw_manager *amd_manager);
+extern const struct sdw_master_ops amd_acp7x_sdw_ops;
+extern const struct sdw_master_port_ops amd_acp7x_sdw_port_ops;
+
#define AMD_SDW_HW_OPS(amd_manager, cb) ((amd_manager)->hw_ops->cb)
/**
diff --git a/drivers/soundwire/amd_manager_common.c b/drivers/soundwire/amd_manager_common.c
index 7e3c9176e793..6f75b6395e91 100644
--- a/drivers/soundwire/amd_manager_common.c
+++ b/drivers/soundwire/amd_manager_common.c
@@ -437,6 +437,11 @@ static void amd_sdw_update_slave_status_work(struct work_struct *work)
case ACP72_PCI_REV_ID:
amd_acp63_sdw_update_slave_status_work(amd_manager);
break;
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ amd_acp7x_sdw_update_slave_status_work(amd_manager);
+ break;
default:
dev_err(amd_manager->dev, "unexpected acp_rev 0x%x\n", amd_manager->acp_rev);
return;
@@ -479,6 +484,11 @@ static void amd_sdw_irq_thread(struct work_struct *work)
case ACP72_PCI_REV_ID:
amd_acp63_sdw_irq_thread(amd_manager);
break;
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ amd_acp7x_sdw_irq_thread(amd_manager);
+ break;
default:
dev_err(amd_manager->dev, "unexpected acp_rev 0x%x\n", amd_manager->acp_rev);
return;
@@ -669,6 +679,10 @@ static int amd_sdw_hw_init_ops(struct amd_sdw_manager *amd_manager)
case ACP71_PCI_REV_ID:
case ACP72_PCI_REV_ID:
return amd_acp63_sdw_hw_init_ops(amd_manager);
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ return amd_sdw_hw_init_ops_acp7x(amd_manager);
default:
return -EINVAL;
}
@@ -738,6 +752,34 @@ static int amd_sdw_manager_probe(struct platform_device *pdev)
amd_manager->num_dout_ports = AMD_ACP70_SDW_MAX_TX_PORTS;
amd_manager->num_din_ports = AMD_ACP70_SDW_MAX_RX_PORTS;
break;
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID: {
+ u32 sdw_gcap;
+
+ if (amd_manager->instance >= AMD_ACP7X_SDW_MAX_MANAGER_COUNT) {
+ dev_err(dev, "invalid ACP7x manager instance %d\n",
+ amd_manager->instance);
+ return -EINVAL;
+ }
+
+ /*
+ * mmio must be set before reading capabilities; compute the
+ * per-manager base using the ACP7B/7E/7F stride and set it early.
+ */
+ amd_manager->mmio = amd_manager->acp_mmio +
+ (amd_manager->instance * ACP7X_SDW_MANAGER_REG_OFFSET);
+ sdw_gcap = readl(amd_manager->mmio + ACP7X_SW_GLOBAL_CAPABILITIES);
+ amd_manager->num_dout_ports = sdw_gcap & ACP7X_SDW_NO_OF_TX_DATA_PORTS;
+ amd_manager->num_din_ports = (sdw_gcap & ACP7X_SDW_NO_OF_RX_DATA_PORTS) >> 4;
+ if (amd_manager->num_dout_ports != ACP7X_SDW_MAX_DAI / 2 ||
+ amd_manager->num_din_ports != ACP7X_SDW_MAX_DAI / 2) {
+ dev_err(dev, "invalid ACP7x port count: tx %d, rx %d\n",
+ amd_manager->num_dout_ports, amd_manager->num_din_ports);
+ return -EINVAL;
+ }
+ break;
+ }
default:
dev_err(dev, "unsupported ACP revision: 0x%x\n", amd_manager->acp_rev);
return -EINVAL;
@@ -759,6 +801,13 @@ static int amd_sdw_manager_probe(struct platform_device *pdev)
if (!amd_manager->port_offset_map)
return -ENOMEM;
break;
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ /* mmio already set in the port-count switch above */
+ amd_manager->bus.ops = &amd_acp7x_sdw_ops;
+ amd_manager->bus.port_ops = &amd_acp7x_sdw_port_ops;
+ break;
default:
dev_err(dev, "unsupported ACP revision: 0x%x\n", amd_manager->acp_rev);
return -EINVAL;
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 03/11] soundwire: amd: extend ctl_word_prep to set SSP_TAG for ACP7B/7E/7F
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
2026-11-07 22:07 ` [PATCH 01/11] soundwire: amd: add ACP7B/7E/7F register definitions and revision IDs Vijendar Mukunda
2026-11-07 22:07 ` [PATCH 02/11] soundwire: amd: add ACP7B/7E/7F hardware backend Vijendar Mukunda
@ 2026-11-07 22:07 ` Vijendar Mukunda
2026-11-07 22:07 ` [PATCH 04/11] soundwire: amd: add ACP7B/7E/7F DAI PCM capabilities in register_dais() Vijendar Mukunda
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:07 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
ACP7B/7E/7F SoundWire hardware requires the SSP_TAG field in the
upper command word to be set from msg->ssp_sync for each transfer.
Add an acp_rev parameter to amd_sdw_ctl_word_prep() and use a
switch/case to conditionally set AMD_SDW_MCP_CMD_SSP_TAG only for
ACP7B/7E/7F revisions. ACP6.3/7.0/7.1/7.2 do not use this field.
Update the three call sites to pass amd_manager->acp_rev.
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
---
drivers/soundwire/amd_manager_common.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/drivers/soundwire/amd_manager_common.c b/drivers/soundwire/amd_manager_common.c
index 6f75b6395e91..0234ffec71f9 100644
--- a/drivers/soundwire/amd_manager_common.c
+++ b/drivers/soundwire/amd_manager_common.c
@@ -28,7 +28,7 @@
#define to_amd_sdw(b) container_of(b, struct amd_sdw_manager, bus)
static void amd_sdw_ctl_word_prep(u32 *lower_word, u32 *upper_word, struct sdw_msg *msg,
- int cmd_offset)
+ int cmd_offset, u32 acp_rev)
{
u32 upper_data;
u32 lower_data = 0;
@@ -45,6 +45,20 @@ static void amd_sdw_ctl_word_prep(u32 *lower_word, u32 *upper_word, struct sdw_m
upper_data = FIELD_PREP(AMD_SDW_MCP_CMD_DEV_ADDR, msg->dev_num);
upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_COMMAND, msg->flags + 2);
+ switch (acp_rev) {
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_SSP_TAG, msg->ssp_sync);
+ break;
+ case ACP63_PCI_REV_ID:
+ case ACP70_PCI_REV_ID:
+ case ACP71_PCI_REV_ID:
+ case ACP72_PCI_REV_ID:
+ break;
+ default:
+ return;
+ }
upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_HIGH, upper_addr);
lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_LOW, lower_addr);
lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_DATA, data);
@@ -65,7 +79,7 @@ amd_program_scp_addr(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg)
scp_msg.addr = SDW_SCP_ADDRPAGE1;
scp_msg.buf = &msg->addr_page1;
scp_msg.flags = SDW_MSG_FLAG_WRITE;
- amd_sdw_ctl_word_prep(&lower_data, &upper_data, &scp_msg, 0);
+ amd_sdw_ctl_word_prep(&lower_data, &upper_data, &scp_msg, 0, amd_manager->acp_rev);
if (acp_sdw_send_cmd_get_resp(amd_manager, lower_data, upper_data, &response_buf[0])) {
dev_err_ratelimited(amd_manager->dev,
"SCP_addrpage command timeout for Slave %d\n", msg->dev_num);
@@ -73,7 +87,7 @@ amd_program_scp_addr(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg)
}
scp_msg.addr = SDW_SCP_ADDRPAGE2;
scp_msg.buf = &msg->addr_page2;
- amd_sdw_ctl_word_prep(&lower_data, &upper_data, &scp_msg, 0);
+ amd_sdw_ctl_word_prep(&lower_data, &upper_data, &scp_msg, 0, amd_manager->acp_rev);
if (acp_sdw_send_cmd_get_resp(amd_manager, lower_data, upper_data, &response_buf[1])) {
dev_err_ratelimited(amd_manager->dev,
"SCP_addrpage command timeout for Slave %d\n", msg->dev_num);
@@ -146,7 +160,7 @@ static unsigned int _amd_sdw_xfer_msg(struct amd_sdw_manager *amd_manager, struc
u64 response;
u32 upper_data = 0, lower_data = 0;
- amd_sdw_ctl_word_prep(&lower_data, &upper_data, msg, cmd_offset);
+ amd_sdw_ctl_word_prep(&lower_data, &upper_data, msg, cmd_offset, amd_manager->acp_rev);
if (acp_sdw_send_cmd_get_resp(amd_manager, lower_data, upper_data, &response)) {
dev_err_ratelimited(amd_manager->dev, "command timeout for Slave %d\n",
msg->dev_num);
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 04/11] soundwire: amd: add ACP7B/7E/7F DAI PCM capabilities in register_dais()
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
` (2 preceding siblings ...)
2026-11-07 22:07 ` [PATCH 03/11] soundwire: amd: extend ctl_word_prep to set SSP_TAG for ACP7B/7E/7F Vijendar Mukunda
@ 2026-11-07 22:07 ` Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 05/11] soundwire: amd: enable host wake support for ACP7B/7E/7F Vijendar Mukunda
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:07 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
ACP7B/7E/7F SoundWire hardware supports higher sample rates and
additional sample formats beyond those of ACP6.3/7.0/7.1/7.2.
Extend the acp_rev switch/case in amd_sdw_register_dais() to set
48 kHz / 96 kHz / 192 kHz rates and S16/S24/S32 formats for
ACP7B/7E/7F DAI streams.
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
---
drivers/soundwire/amd_manager_common.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/soundwire/amd_manager_common.c b/drivers/soundwire/amd_manager_common.c
index 0234ffec71f9..b16fd5b032ec 100644
--- a/drivers/soundwire/amd_manager_common.c
+++ b/drivers/soundwire/amd_manager_common.c
@@ -425,6 +425,14 @@ static int amd_sdw_register_dais(struct amd_sdw_manager *amd_manager)
stream->rates = SNDRV_PCM_RATE_48000;
stream->formats = SNDRV_PCM_FMTBIT_S16_LE;
break;
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ stream->rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000;
+ stream->formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE;
+ break;
default:
dev_err(amd_manager->dev, "unexpected acp_rev 0x%x\n",
amd_manager->acp_rev);
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 05/11] soundwire: amd: enable host wake support for ACP7B/7E/7F
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
` (3 preceding siblings ...)
2026-11-07 22:07 ` [PATCH 04/11] soundwire: amd: add ACP7B/7E/7F DAI PCM capabilities in register_dais() Vijendar Mukunda
@ 2026-11-07 22:08 ` Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 06/11] soundwire: amd: add ACP7B/7E/7F clock-stop support Vijendar Mukunda
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:08 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
amd_sdw_host_wake_enable_by_rev() previously had no case for
ACP7B/7E/7F, so host wake enable/disable calls would fall through
to the default error path on those revisions.
Add ACP7B/7E/7F to the switch/case alongside ACP70/71/72 so that
acp_sdw_host_wake_enable() is invoked, which dispatches to the
per-revision hw_ops->host_wake_enable callback.
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
---
drivers/soundwire/amd_manager_common.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/soundwire/amd_manager_common.c b/drivers/soundwire/amd_manager_common.c
index b16fd5b032ec..1f0575c6942b 100644
--- a/drivers/soundwire/amd_manager_common.c
+++ b/drivers/soundwire/amd_manager_common.c
@@ -670,6 +670,9 @@ int amd_sdw_host_wake_enable_by_rev(struct amd_sdw_manager *amd_manager, bool en
case ACP70_PCI_REV_ID:
case ACP71_PCI_REV_ID:
case ACP72_PCI_REV_ID:
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
return acp_sdw_host_wake_enable(amd_manager, enable);
default:
dev_err(amd_manager->dev, "unexpected acp_rev 0x%x\n", amd_manager->acp_rev);
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 06/11] soundwire: amd: add ACP7B/7E/7F clock-stop support
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
` (4 preceding siblings ...)
2026-11-07 22:08 ` [PATCH 05/11] soundwire: amd: enable host wake support for ACP7B/7E/7F Vijendar Mukunda
@ 2026-11-07 22:08 ` Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 07/11] soundwire: amd: add IO pad enable/disable for ACP7B/7E/7F D0/D3 sequence Vijendar Mukunda
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:08 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
Extend amd_sdw_clock_stop() to handle ACP7B/7E/7F revisions.
Set clk_resume_ctrl to ACP7X_SW_CLK_RESUME_CTRL for the ACP7x
clock-stop sequence. For wake enable, ACP7B/7E/7F use a single
shared ACP7X_SW_WAKE_EN register with one bit per manager instance,
unlike older revisions which have dedicated per-instance registers.
Perform a read-modify-write under acp_sdw_lock to safely set the
instance bit without racing against other manager instances.
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
---
drivers/soundwire/amd_manager_common.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/drivers/soundwire/amd_manager_common.c b/drivers/soundwire/amd_manager_common.c
index 1f0575c6942b..158845f67224 100644
--- a/drivers/soundwire/amd_manager_common.c
+++ b/drivers/soundwire/amd_manager_common.c
@@ -553,7 +553,7 @@ int amd_sdw_manager_start(struct amd_sdw_manager *amd_manager)
*/
int amd_sdw_clock_stop(struct amd_sdw_manager *amd_manager)
{
- u32 clk_resume_ctrl, val;
+ u32 clk_resume_ctrl, val, wake_en;
int ret;
ret = sdw_bus_prep_clk_stop(&amd_manager->bus);
@@ -574,6 +574,11 @@ int amd_sdw_clock_stop(struct amd_sdw_manager *amd_manager)
case ACP72_PCI_REV_ID:
clk_resume_ctrl = ACP_SW_CLK_RESUME_CTRL;
break;
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ clk_resume_ctrl = ACP7X_SW_CLK_RESUME_CTRL;
+ break;
default:
dev_err(amd_manager->dev, "unexpected acp_rev 0x%x\n", amd_manager->acp_rev);
return -EINVAL;
@@ -596,6 +601,15 @@ int amd_sdw_clock_stop(struct amd_sdw_manager *amd_manager)
case ACP72_PCI_REV_ID:
writel(0x01, amd_manager->acp_mmio + ACP_SW_WAKE_EN(amd_manager->instance));
break;
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ mutex_lock(amd_manager->acp_sdw_lock);
+ wake_en = readl(amd_manager->acp_mmio + ACP7X_SW_WAKE_EN);
+ wake_en |= BIT(amd_manager->instance);
+ writel(wake_en, amd_manager->acp_mmio + ACP7X_SW_WAKE_EN);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+ break;
default:
dev_err(amd_manager->dev, "unexpected acp_rev 0x%x\n",
amd_manager->acp_rev);
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 07/11] soundwire: amd: add IO pad enable/disable for ACP7B/7E/7F D0/D3 sequence
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
` (5 preceding siblings ...)
2026-11-07 22:08 ` [PATCH 06/11] soundwire: amd: add ACP7B/7E/7F clock-stop support Vijendar Mukunda
@ 2026-11-07 22:08 ` Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 08/11] soundwire: generic_bandwidth_allocation: fix hwidth for skipped block offset Vijendar Mukunda
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:08 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
ACP7B/7E/7F SoundWire IO pad registers (ACP_SW_PAD_KEEPER_EN,
ACP7X_PAD_SCHMEN_CTRL, ACP7X_PAD_DISABLE_OE_CTRL) are reset when the
ACP power domain is removed at D3. Without restoring them on D0 resume
the output drivers are tri-stated and SoundWire IO cannot function.
Add pad control functions for ACP7B/7E/7F in amd_init.c:
- amd_acp7x_sdw_pads_save_masks(): compute per-instance bitmasks from
ACP7X_PIN_CONFIG and revision-specific bump-pin tables; save to
amd_sdw_manager for use by suspend/resume paths.
- amd_acp7x_sdw_pads_enable(): restore all three pad registers in the
HW-required order (KEEPER_EN -> SCHMEN_CTRL -> DISABLE_OE_CTRL) on
D0 resume after a power-off cycle that cleared all pads.
- amd_acp7x_sdw_pads_disable(): tri-state output drivers (DISABLE_OE
only) for runtime D3 with wake enabled.
- amd_acp7x_sdw_pads_oe_ctrl(): OE-only toggle for clock-stop mode;
equivalent to amd_acp63_sdw_oe_ctrl() for ACP7.0/7.1/7.2.
- amd_acp7x_sdw_pads_clear_all(): clears all three pad registers in
reverse order (OE -> SCHMEN -> KEEPER) on device removal or
power-off system suspend.
Wire the pad helpers into PM paths in amd_acp7x_manager.c:
- Clock-stop suspend (runtime and system): disable OE only via
pads_oe_ctrl(false); the ACP power domain stays intact so keeper
and schmen registers are preserved.
- Clock-stop resume: re-enable OE via pads_oe_ctrl(true).
- Power-off runtime suspend: tri-state OE via pads_disable(); wake
interrupt path remains active.
- Power-off system suspend: clear all three pad registers via
pads_clear_all() and set pad_fully_torn_down flag.
- Power-off resume: if pad_fully_torn_down is set, restore all pads
via pads_enable() and clear the flag; otherwise restore OE only
via pads_oe_ctrl(true). Full bus re-enumeration follows in both
cases.
In amd_manager_common.c amd_sdw_manager_start(), add explicit cases
for ACP6.3/7.0/7.1/7.2 (break, no pad masks needed) and return
-EINVAL for unrecognised revisions. For ACP7B/7E/7F, propagate any
error from pads_save_masks() as a hard failure so the driver does
not start with uninitialised pad masks.
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
---
drivers/soundwire/amd_acp7x_manager.c | 14 +
drivers/soundwire/amd_init.c | 365 ++++++++++++++++++++++++-
drivers/soundwire/amd_init.h | 13 +
drivers/soundwire/amd_manager_common.c | 31 +++
include/linux/soundwire/sdw_amd.h | 20 +-
5 files changed, 436 insertions(+), 7 deletions(-)
diff --git a/drivers/soundwire/amd_acp7x_manager.c b/drivers/soundwire/amd_acp7x_manager.c
index 33fc9ced48b6..721d4e97371b 100644
--- a/drivers/soundwire/amd_acp7x_manager.c
+++ b/drivers/soundwire/amd_acp7x_manager.c
@@ -636,6 +636,7 @@ static int amd_acp7x_sdw_suspend(struct amd_sdw_manager *amd_manager)
ret = amd_sdw_clock_stop(amd_manager);
if (ret)
return ret;
+ amd_acp7x_sdw_pads_oe_ctrl(amd_manager, false);
acp_enable_sdw_interrupts(amd_manager);
} else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
acp_disable_sdw_interrupts(amd_manager);
@@ -651,6 +652,8 @@ static int amd_acp7x_sdw_suspend(struct amd_sdw_manager *amd_manager)
ret = acp_disable_sdw_manager(amd_manager);
if (ret)
return ret;
+ amd_acp7x_sdw_pads_clear_all(amd_manager);
+ amd_manager->pad_fully_torn_down = true;
}
return acp_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D3);
}
@@ -675,6 +678,7 @@ static int amd_acp7x_sdw_suspend_runtime(struct amd_sdw_manager *amd_manager)
ret = amd_sdw_clock_stop(amd_manager);
if (ret)
return ret;
+ amd_acp7x_sdw_pads_oe_ctrl(amd_manager, false);
acp_enable_sdw_interrupts(amd_manager);
} else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
acp_disable_sdw_interrupts(amd_manager);
@@ -690,6 +694,7 @@ static int amd_acp7x_sdw_suspend_runtime(struct amd_sdw_manager *amd_manager)
ret = acp_disable_sdw_manager(amd_manager);
if (ret)
return ret;
+ amd_acp7x_sdw_pads_disable(amd_manager);
}
ret = acp_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D3);
if (ret)
@@ -719,6 +724,7 @@ static int amd_acp7x_sdw_resume_runtime(struct amd_sdw_manager *amd_manager)
ret = amd_sdw_host_wake_enable_by_rev(amd_manager, false);
if (ret)
return ret;
+ amd_acp7x_sdw_pads_oe_ctrl(amd_manager, true);
ret = acp_sdw_clock_stop_exit(amd_manager);
if (ret)
return ret;
@@ -731,6 +737,14 @@ static int amd_acp7x_sdw_resume_runtime(struct amd_sdw_manager *amd_manager)
ret = amd_sdw_host_wake_enable_by_rev(amd_manager, false);
if (ret)
return ret;
+ if (amd_manager->pad_fully_torn_down) {
+ ret = amd_acp7x_sdw_pads_enable(amd_manager);
+ if (ret)
+ return ret;
+ amd_manager->pad_fully_torn_down = false;
+ } else {
+ amd_acp7x_sdw_pads_oe_ctrl(amd_manager, true);
+ }
sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
ret = acp_sdw_clk_init_ctrl(amd_manager);
if (ret)
diff --git a/drivers/soundwire/amd_init.c b/drivers/soundwire/amd_init.c
index d8e559439cef..12b7801964a5 100644
--- a/drivers/soundwire/amd_init.c
+++ b/drivers/soundwire/amd_init.c
@@ -16,6 +16,99 @@
#include "amd_init.h"
+/*
+ * Pin-config group: maps {clock, data[0..3]} to bump-pin indices per manager.
+ * -1 indicates the pin is not present for that lane.
+ */
+struct sdw_pin_config_group {
+ s8 clock;
+ s8 data[4];
+};
+
+/*
+ * Clock and data bump-pin assignments per manager instance.
+ * Data[0] is the primary (single-lane) data pad used for non-GSYNC operation.
+ */
+static const struct sdw_pin_config_group acp7b_pin_config[AMD_ACP7X_SDW_MAX_MANAGER_COUNT] = {
+ [ACP_SDW0] = { .clock = 0, .data = { 1, 2, 3, 5 } },
+ [ACP_SDW1] = { .clock = 2, .data = { 3, 5, 7, 1 } },
+ [ACP_SDW2] = { .clock = 4, .data = { 5, 7, 1, 2 } },
+ [ACP_SDW3] = { .clock = 6, .data = { 7, 1, 2, 5 } },
+};
+
+/*
+ * ACP7E bump-pin assignments per manager instance.
+ * SW0: CLK=BumpPin_5, DATA={BumpPin_6,1,3,4}
+ * SW1: CLK=BumpPin_0, DATA={BumpPin_2}
+ * SW2: CLK=BumpPin_5, DATA={BumpPin_6,1,3,4}
+ * SW3: CLK=BumpPin_0, DATA={BumpPin_2}
+ */
+static const struct sdw_pin_config_group acp7e_pin_config[AMD_ACP7X_SDW_MAX_MANAGER_COUNT] = {
+ [ACP_SDW0] = { .clock = 5, .data = { 6, 1, 3, 4 } },
+ [ACP_SDW1] = { .clock = 0, .data = { 2, -1, -1, -1 } },
+ [ACP_SDW2] = { .clock = 5, .data = { 6, 1, 3, 4 } },
+ [ACP_SDW3] = { .clock = 0, .data = { 2, -1, -1, -1 } },
+};
+
+/*
+ * ACP7F bump-pin assignments per manager instance.
+ * SW2 uses BumpPin_8 (clock) and BumpPin_1 (data) in multi-link configs.
+ */
+static const struct sdw_pin_config_group acp7f_pin_config[AMD_ACP7X_SDW_MAX_MANAGER_COUNT] = {
+ [ACP_SDW0] = { .clock = 5, .data = { 6, 1, 3, 4 } },
+ [ACP_SDW1] = { .clock = 0, .data = { 2, -1, -1, -1 } },
+ [ACP_SDW2] = { .clock = 8, .data = { 1, -1, -1, -1 } },
+ [ACP_SDW3] = { .clock = 0, .data = { 2, -1, -1, -1 } },
+};
+
+/*
+ * Bump-pin to bit-position mappings for pad control registers.
+ * Index = bump pin, value = bit position (-1 = not present for this revision).
+ * BumpPin_0 has no keeper-control bit on ACP7B/7E/7F, but it has
+ * Schmitt-trigger and output-enable control bits.
+ * ACP7B also has no keeper-control bits for BumpPins 4, 6, and 8.
+ *
+ * Keeper register:
+ * ACP7B: { -1, 0, 1, 2, -1, 3, -1, 4, -1 }
+ * ACP7E: { -1, 1, 4, 2, 3, -1, 0, -1, -1 }
+ * ACP7F: { -1, 1, 4, 2, 3, -1, 0, 1, 1 }
+ *
+ * ACP7X_PAD_SCHMEN_CTRL and ACP7X_PAD_DISABLE_OE_CTRL use the same identity
+ * mapping per revision. The separate disable_oe_map pointer allows future
+ * revisions to use a different output-disable layout.
+ *
+ * ACP7B: { 0, 1, 2, 3, 4, 5, 6, 7, -1 }
+ * ACP7E: { 0, 1, 2, 3, 4, 5, 6, -1, -1 }
+ * ACP7F: { 0, 1, 2, 3, 4, 5, 6, 7, 8 }
+ */
+static const s8 acp7b_pad_to_bit_keeper[AMD_ACP7X_BUMP_PIN_COUNT] = {
+ -1, 0, 1, 2, -1, 3, -1, 4, -1,
+};
+
+static const s8 acp7e_pad_to_bit_keeper[AMD_ACP7X_BUMP_PIN_COUNT] = {
+ -1, 1, 4, 2, 3, -1, 0, -1, -1,
+};
+
+/* ACP7F: BumpPin_1 (SW2 data) and BumpPin_8 (SW2 clock) both map to
+ * keeper bit 1 - this is intentional HW layout for the ACP7F SoundWire
+ * SW2 instance where both pads share the same keeper control bit.
+ */
+static const s8 acp7f_pad_to_bit_keeper[AMD_ACP7X_BUMP_PIN_COUNT] = {
+ -1, 1, 4, 2, 3, -1, 0, 1, 1,
+};
+
+static const s8 acp7b_pad_to_bit_schmen[AMD_ACP7X_BUMP_PIN_COUNT] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, -1,
+};
+
+static const s8 acp7e_pad_to_bit_schmen[AMD_ACP7X_BUMP_PIN_COUNT] = {
+ 0, 1, 2, 3, 4, 5, 6, -1, -1,
+};
+
+static const s8 acp7f_pad_to_bit_schmen[AMD_ACP7X_BUMP_PIN_COUNT] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8,
+};
+
static int amd_acp63_enable_sdw_pads(void __iomem *mmio, u32 link_mask,
u32 acp_rev, struct device *dev)
{
@@ -64,6 +157,254 @@ static int amd_acp63_enable_sdw_pads(void __iomem *mmio, u32 link_mask,
return 0;
}
+/*
+ * Compute bitmask for a pad control register given a manager's bump-pin layout.
+ * Only the clock and primary data pad (lane 0) are programmed for single-lane use.
+ */
+static u32 acp7x_sdw_compute_pad_bit_mask(const struct sdw_pin_config_group *cfg,
+ const s8 *bit_map)
+{
+ u32 mask = 0;
+ s8 bit;
+
+ if (cfg->clock >= 0 && cfg->clock < AMD_ACP7X_BUMP_PIN_COUNT) {
+ bit = bit_map[cfg->clock];
+ if (bit >= 0)
+ mask |= BIT(bit);
+ }
+
+ if (cfg->data[0] >= 0 && cfg->data[0] < AMD_ACP7X_BUMP_PIN_COUNT) {
+ bit = bit_map[cfg->data[0]];
+ if (bit >= 0)
+ mask |= BIT(bit);
+ }
+
+ return mask;
+}
+
+static int amd_acp7x_select_pad_tables(u32 acp_rev, struct device *dev,
+ const struct sdw_pin_config_group **table,
+ const s8 **keeper_map, const s8 **schmen_map,
+ const s8 **disable_oe_map, u32 *max_pin_config)
+{
+ switch (acp_rev) {
+ case ACP7B_PCI_REV_ID:
+ *max_pin_config = ACP7B_MAX_PIN_CONFIG;
+ *table = acp7b_pin_config;
+ *keeper_map = acp7b_pad_to_bit_keeper;
+ *schmen_map = acp7b_pad_to_bit_schmen;
+ *disable_oe_map = acp7b_pad_to_bit_schmen;
+ break;
+ case ACP7E_PCI_REV_ID:
+ *max_pin_config = ACP7E_MAX_PIN_CONFIG;
+ *table = acp7e_pin_config;
+ *keeper_map = acp7e_pad_to_bit_keeper;
+ *schmen_map = acp7e_pad_to_bit_schmen;
+ *disable_oe_map = acp7e_pad_to_bit_schmen;
+ break;
+ case ACP7F_PCI_REV_ID:
+ *max_pin_config = ACP7F_MAX_PIN_CONFIG;
+ *table = acp7f_pin_config;
+ *keeper_map = acp7f_pad_to_bit_keeper;
+ *schmen_map = acp7f_pad_to_bit_schmen;
+ *disable_oe_map = acp7f_pad_to_bit_schmen;
+ break;
+ default:
+ dev_err(dev, "%s: unsupported ACP rev 0x%x\n", __func__, acp_rev);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static int amd_acp7x_enable_sdw_pads(void __iomem *mmio, u32 link_mask, struct device *dev,
+ u32 acp_rev)
+{
+ const struct sdw_pin_config_group *pin_cfg_table;
+ const s8 *keeper_map, *schmen_map, *disable_oe_map;
+ u32 max_pin_config;
+ u32 pad_keeper_en = 0;
+ u32 pad_schmen = 0;
+ u32 pad_disable_oe_mask = 0;
+ u32 pin_config;
+ int i, ret;
+
+ pin_config = readl(mmio + ACP7X_PIN_CONFIG);
+
+ ret = amd_acp7x_select_pad_tables(acp_rev, dev, &pin_cfg_table,
+ &keeper_map, &schmen_map,
+ &disable_oe_map, &max_pin_config);
+ if (ret)
+ return ret;
+
+ if (pin_config == 0 || pin_config > max_pin_config) {
+ dev_err(dev, "Unsupported ACP7X_PIN_CONFIG:0x%x for ACP Rev:0x%x\n",
+ pin_config, acp_rev);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < AMD_ACP7X_SDW_MAX_MANAGER_COUNT; i++) {
+ if (!(link_mask & BIT(i)))
+ continue;
+ pad_keeper_en |= acp7x_sdw_compute_pad_bit_mask(&pin_cfg_table[i], keeper_map);
+ pad_schmen |= acp7x_sdw_compute_pad_bit_mask(&pin_cfg_table[i], schmen_map);
+ pad_disable_oe_mask |= acp7x_sdw_compute_pad_bit_mask(&pin_cfg_table[i],
+ disable_oe_map);
+ }
+
+ /*
+ * Program pad control registers in HW-required sequence:
+ * 1. KEEPER_EN - latch/keep pad state
+ * 2. SCHMEN_CTRL - enable Schmitt trigger on input pads
+ * 3. DISABLE_OE_CTRL - clear output-disable bits to enable output drivers
+ */
+ if (pad_keeper_en)
+ amd_updatel(mmio, ACP_SW_PAD_KEEPER_EN, pad_keeper_en, pad_keeper_en);
+
+ if (pad_schmen)
+ amd_updatel(mmio, ACP7X_PAD_SCHMEN_CTRL, pad_schmen, pad_schmen);
+
+ if (pad_disable_oe_mask)
+ amd_updatel(mmio, ACP7X_PAD_DISABLE_OE_CTRL, pad_disable_oe_mask, 0);
+
+ return 0;
+}
+
+static int amd_acp7x_sdw_pads_compute_masks(struct amd_sdw_manager *amd_manager,
+ u32 *keeper_en, u32 *schmen, u32 *disable_oe)
+{
+ const struct sdw_pin_config_group *pin_cfg_table;
+ const s8 *keeper_map, *schmen_map, *disable_oe_map;
+ u32 max_pin_config;
+ u32 pin_config;
+ int ret;
+
+ pin_config = readl(amd_manager->acp_mmio + ACP7X_PIN_CONFIG);
+
+ ret = amd_acp7x_select_pad_tables(amd_manager->acp_rev, amd_manager->dev,
+ &pin_cfg_table, &keeper_map, &schmen_map,
+ &disable_oe_map, &max_pin_config);
+ if (ret)
+ return ret;
+
+ if (pin_config == 0 || pin_config > max_pin_config) {
+ dev_err(amd_manager->dev,
+ "%s unsupported ACP7X_PIN_CONFIG 0x%x for rev 0x%x\n",
+ __func__, pin_config, amd_manager->acp_rev);
+ return -ENODEV;
+ }
+
+ *keeper_en = acp7x_sdw_compute_pad_bit_mask(&pin_cfg_table[amd_manager->instance],
+ keeper_map);
+ *schmen = acp7x_sdw_compute_pad_bit_mask(&pin_cfg_table[amd_manager->instance],
+ schmen_map);
+ *disable_oe = acp7x_sdw_compute_pad_bit_mask(&pin_cfg_table[amd_manager->instance],
+ disable_oe_map);
+ return 0;
+}
+
+int amd_acp7x_sdw_pads_save_masks(struct amd_sdw_manager *amd_manager)
+{
+ u32 pad_keeper_en, pad_schmen, pad_disable_oe_mask;
+ int ret;
+
+ ret = amd_acp7x_sdw_pads_compute_masks(amd_manager, &pad_keeper_en,
+ &pad_schmen, &pad_disable_oe_mask);
+ if (ret)
+ return ret;
+
+ amd_manager->pad_keeper_en = pad_keeper_en;
+ amd_manager->pad_schmen = pad_schmen;
+ amd_manager->pad_disable_oe_mask = pad_disable_oe_mask;
+ return 0;
+}
+
+int amd_acp7x_sdw_pads_enable(struct amd_sdw_manager *amd_manager)
+{
+ u32 pad_keeper_en, pad_schmen, pad_disable_oe_mask;
+ int ret;
+
+ ret = amd_acp7x_sdw_pads_compute_masks(amd_manager, &pad_keeper_en,
+ &pad_schmen, &pad_disable_oe_mask);
+ if (ret)
+ return ret;
+
+ /* Program in HW-required sequence: KEEPER -> SCHMEN -> DISABLE_OE */
+ mutex_lock(amd_manager->acp_sdw_lock);
+ if (pad_keeper_en)
+ amd_updatel(amd_manager->acp_mmio, ACP_SW_PAD_KEEPER_EN,
+ pad_keeper_en, pad_keeper_en);
+ if (pad_schmen)
+ amd_updatel(amd_manager->acp_mmio, ACP7X_PAD_SCHMEN_CTRL,
+ pad_schmen, pad_schmen);
+ if (pad_disable_oe_mask)
+ amd_updatel(amd_manager->acp_mmio, ACP7X_PAD_DISABLE_OE_CTRL,
+ pad_disable_oe_mask, 0);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+
+ amd_manager->pad_keeper_en = pad_keeper_en;
+ amd_manager->pad_schmen = pad_schmen;
+ amd_manager->pad_disable_oe_mask = pad_disable_oe_mask;
+ return 0;
+}
+
+void amd_acp7x_sdw_pads_disable(struct amd_sdw_manager *amd_manager)
+{
+ if (!amd_manager->pad_disable_oe_mask)
+ return;
+
+ mutex_lock(amd_manager->acp_sdw_lock);
+ amd_updatel(amd_manager->acp_mmio, ACP7X_PAD_DISABLE_OE_CTRL,
+ amd_manager->pad_disable_oe_mask,
+ amd_manager->pad_disable_oe_mask);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+}
+
+void amd_acp7x_sdw_pads_clear_all(struct amd_sdw_manager *amd_manager)
+{
+ /* Clear all pad registers in reverse order: OE -> Schmen -> Keeper */
+ mutex_lock(amd_manager->acp_sdw_lock);
+ if (amd_manager->pad_disable_oe_mask)
+ amd_updatel(amd_manager->acp_mmio, ACP7X_PAD_DISABLE_OE_CTRL,
+ amd_manager->pad_disable_oe_mask,
+ amd_manager->pad_disable_oe_mask);
+
+ if (amd_manager->pad_schmen)
+ amd_updatel(amd_manager->acp_mmio, ACP7X_PAD_SCHMEN_CTRL,
+ amd_manager->pad_schmen, 0);
+
+ if (amd_manager->pad_keeper_en)
+ amd_updatel(amd_manager->acp_mmio, ACP_SW_PAD_KEEPER_EN,
+ amd_manager->pad_keeper_en, 0);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+}
+
+/*
+ * amd_acp7x_sdw_pads_oe_ctrl - enable or disable OE control for ACP7B/7E/7F
+ *
+ * Clock-stop suspend/resume sequence (power domain stays on):
+ * suspend: disable OE (set DISABLE_OE bits) to tri-state outputs
+ * resume: enable OE (clear DISABLE_OE bits) to drive outputs again
+ *
+ * Uses pad_disable_oe_mask saved at probe time by amd_acp7x_sdw_pads_save_masks()
+ * and reused for every clock-stop cycle.
+ * Equivalent to amd_acp63_sdw_oe_ctrl() used by ACP6.3/7.0/7.1/7.2.
+ */
+void amd_acp7x_sdw_pads_oe_ctrl(struct amd_sdw_manager *amd_manager, bool enable)
+{
+ if (!amd_manager->pad_disable_oe_mask)
+ return;
+
+ mutex_lock(amd_manager->acp_sdw_lock);
+ /*
+ * DISABLE_OE register: set bits = tri-state (OE off),
+ * clear bits = drive (OE on).
+ */
+ amd_updatel(amd_manager->acp_mmio, ACP7X_PAD_DISABLE_OE_CTRL,
+ amd_manager->pad_disable_oe_mask,
+ enable ? 0 : amd_manager->pad_disable_oe_mask);
+ mutex_unlock(amd_manager->acp_sdw_lock);
+}
+
static int sdw_amd_cleanup(struct sdw_amd_ctx *ctx)
{
int i;
@@ -107,12 +448,34 @@ static struct sdw_amd_ctx *sdw_amd_probe_controller(struct sdw_amd_res *res)
case ACP72_PCI_REV_ID:
max_manager_count = AMD_ACP63_SDW_MAX_MANAGER_COUNT;
break;
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ max_manager_count = AMD_ACP7X_SDW_MAX_MANAGER_COUNT;
+ break;
default:
dev_err(&adev->dev, "unsupported ACP revision: 0x%x\n", res->acp_rev);
return NULL;
}
- ret = amd_acp63_enable_sdw_pads(res->mmio_base, res->link_mask, res->acp_rev, res->parent);
+ switch (res->acp_rev) {
+ case ACP63_PCI_REV_ID:
+ case ACP70_PCI_REV_ID:
+ case ACP71_PCI_REV_ID:
+ case ACP72_PCI_REV_ID:
+ ret = amd_acp63_enable_sdw_pads(res->mmio_base, res->link_mask,
+ res->acp_rev, res->parent);
+ break;
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ ret = amd_acp7x_enable_sdw_pads(res->mmio_base, res->link_mask,
+ res->parent, res->acp_rev);
+ break;
+ default:
+ dev_err(&adev->dev, "unsupported ACP revision: 0x%x\n", res->acp_rev);
+ return NULL;
+ }
if (ret)
return NULL;
diff --git a/drivers/soundwire/amd_init.h b/drivers/soundwire/amd_init.h
index 8324bc7a147f..775a4feb1a0c 100644
--- a/drivers/soundwire/amd_init.h
+++ b/drivers/soundwire/amd_init.h
@@ -12,6 +12,8 @@
#define ACP_PAD_SCHMEN_CTRL 0x0001450
#define ACP_SW_PAD_KEEPER_EN 0x0001454
#define ACP_PAD_DISABLE_OE_CTRL 0x0001468
+#define ACP7X_PAD_SCHMEN_CTRL ACP_PAD_SCHMEN_CTRL
+#define ACP7X_PAD_DISABLE_OE_CTRL ACP_PAD_DISABLE_OE_CTRL
#define AMD_SDW0_PAD_PULLDOWN_MASK (BIT(5) | BIT(6))
#define AMD_SDW1_PAD_PULLDOWN_MASK (BIT(0) | BIT(2))
@@ -26,7 +28,18 @@
#define AMD_SDW1_PAD_SCHMEN_MASK (BIT(0) | BIT(2))
#define AMD_SDW_PAD_SCHMEN_MASK (AMD_SDW0_PAD_SCHMEN_MASK | AMD_SDW1_PAD_SCHMEN_MASK)
+#define ACP7X_PIN_CONFIG 0x0001440
+#define AMD_ACP7X_BUMP_PIN_COUNT 9
+#define ACP7B_MAX_PIN_CONFIG 0x16
+#define ACP7E_MAX_PIN_CONFIG 0x1d
+#define ACP7F_MAX_PIN_CONFIG 0x2a
+
int amd_sdw_manager_start(struct amd_sdw_manager *amd_manager);
+int amd_acp7x_sdw_pads_save_masks(struct amd_sdw_manager *amd_manager);
+int amd_acp7x_sdw_pads_enable(struct amd_sdw_manager *amd_manager);
+void amd_acp7x_sdw_pads_disable(struct amd_sdw_manager *amd_manager);
+void amd_acp7x_sdw_pads_oe_ctrl(struct amd_sdw_manager *amd_manager, bool enable);
+void amd_acp7x_sdw_pads_clear_all(struct amd_sdw_manager *amd_manager);
static inline void amd_updatel(void __iomem *mmio, int offset, u32 mask, u32 val)
{
diff --git a/drivers/soundwire/amd_manager_common.c b/drivers/soundwire/amd_manager_common.c
index 158845f67224..03c92b6991f2 100644
--- a/drivers/soundwire/amd_manager_common.c
+++ b/drivers/soundwire/amd_manager_common.c
@@ -536,6 +536,22 @@ int amd_sdw_manager_start(struct amd_sdw_manager *amd_manager)
return ret;
acp_sdw_set_frameshape(amd_manager);
}
+ switch (amd_manager->acp_rev) {
+ case ACP63_PCI_REV_ID:
+ case ACP70_PCI_REV_ID:
+ case ACP71_PCI_REV_ID:
+ case ACP72_PCI_REV_ID:
+ break;
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ ret = amd_acp7x_sdw_pads_save_masks(amd_manager);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
/* Enable runtime PM */
pm_runtime_set_autosuspend_delay(amd_manager->dev, AMD_SDW_MASTER_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(amd_manager->dev);
@@ -886,6 +902,21 @@ static void amd_sdw_manager_remove(struct platform_device *pdev)
ret = acp_disable_sdw_manager(amd_manager);
if (ret)
dev_err(&pdev->dev, "Failed to disable device (%pe)\n", ERR_PTR(ret));
+ /*
+ * On device removal, clear all ACP7B/7E/7F IO pad registers
+ * (DISABLE_OE -> SCHMEN -> KEEPER) so the pads are fully released
+ * when the power domain is removed permanently. The runtime suspend
+ * path only tri-states OE; full pad release is done on removal.
+ */
+ switch (amd_manager->acp_rev) {
+ case ACP7B_PCI_REV_ID:
+ case ACP7E_PCI_REV_ID:
+ case ACP7F_PCI_REV_ID:
+ amd_acp7x_sdw_pads_clear_all(amd_manager);
+ break;
+ default:
+ break;
+ }
}
static const struct dev_pm_ops amd_pm = {
diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h
index 7dfb17603159..2c09a406fbda 100644
--- a/include/linux/soundwire/sdw_amd.h
+++ b/include/linux/soundwire/sdw_amd.h
@@ -87,16 +87,21 @@ struct amd_sdw_hw_ops;
* @wake_en_mask: wake enable mask per SoundWire manager
* @acp_rev: acp pci device revision id
* @clk_stopped: flag set to true when clock is stopped
- * @pad_fully_torn_down: true when system suspend performed a full pad
- * teardown (keeper, schmen and OE cleared, pulldown asserted). Cleared after
- * all pads are restored in amd_resume_runtime(). False on initial probe
- * (zero-initialized) and after a wake-armed runtime suspend in which
- * only OE was disabled; in the latter case amd_resume_runtime() restores
- * OE only, leaving keeper and schmen untouched.
+ * @pad_fully_torn_down: true when system suspend cleared all three pad
+ * registers (KEEPER_EN, SCHMEN_CTRL and DISABLE_OE_CTRL). Set to false
+ * on initial probe and after a runtime suspend that only disabled OE
+ * (wake enabled path). amd_acp7x_sdw_resume_runtime() checks this flag
+ * to decide whether to restore all pads or OE only.
* @power_mode_mask: flag interprets amd SoundWire manager power mode
* @ping_slave_stat: cached slave status from the last ping
* transaction that returned a response;
* initialised to U32_MAX ("no valid reading yet")
+ * @pad_keeper_en: bitmask saved from ACP_SW_PAD_KEEPER_EN at probe time;
+ * restored in power-off mode D0 resume after a pad power cycle
+ * @pad_schmen: bitmask saved from ACP7X_PAD_SCHMEN_CTRL at probe time;
+ * restored in power-off mode D0 resume
+ * @pad_disable_oe_mask: output-enable disable bitmask saved at probe time;
+ * re-applied (cleared) in power-off mode D0 resume
* @interrupt_enabled: set when hardware interrupts are enabled; cleared
* before disabling to prevent new work from being queued
* by an in-flight IRQ during teardown
@@ -131,6 +136,9 @@ struct amd_sdw_manager {
u32 power_mode_mask;
u32 acp_rev;
u32 ping_slave_stat;
+ u32 pad_keeper_en;
+ u32 pad_schmen;
+ u32 pad_disable_oe_mask;
bool clk_stopped;
bool pad_fully_torn_down;
bool interrupt_enabled;
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 08/11] soundwire: generic_bandwidth_allocation: fix hwidth for skipped block offset
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
` (6 preceding siblings ...)
2026-11-07 22:08 ` [PATCH 07/11] soundwire: amd: add IO pad enable/disable for ACP7B/7E/7F D0/D3 sequence Vijendar Mukunda
@ 2026-11-07 22:08 ` Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 09/11] soundwire: generic_bandwidth_allocation: export bandwidth helper functions Vijendar Mukunda
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:08 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
The hwidth computation in sdw_compute_group_params() used bare
payload_bw when calculating the horizontal columns required for a
stream group. The generic transport programming starts payload block
offsets from block_offset 1, so offset 0 is skipped. Without
accounting for that skipped offset, some frame configurations can
program an hstart/hstop payload window that is too narrow.
Introduce sdw_compute_hwidth() which adds 1 to payload_bw before
the ceiling division to account for the skipped slot. Also skip
groups with zero payload_bw before calling sdw_compute_hwidth(),
and skip groups with a resulting hwidth of 0 to avoid emitting
an invalid hstart/hstop range.
Fixes: 9026118f20e2 ("soundwire: Add generic bandwidth allocation algorithm")
Signed-off-by: Poojitha N <Poojitha.N@amd.com>
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
---
.../soundwire/generic_bandwidth_allocation.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c
index 30a495abe19e..1dd3b2e5420c 100644
--- a/drivers/soundwire/generic_bandwidth_allocation.c
+++ b/drivers/soundwire/generic_bandwidth_allocation.c
@@ -15,6 +15,12 @@
#define SDW_STRM_RATE_GROUPING 1
+static int sdw_compute_hwidth(int col, int payload_bw, int full_bw)
+{
+ /* Account for the initial skipped payload bit at block offset 0. */
+ return (col * (payload_bw + 1) + full_bw - 1) / full_bw;
+}
+
struct sdw_group_params {
unsigned int rate;
unsigned int lane;
@@ -203,6 +209,9 @@ static void _sdw_compute_port_params(struct sdw_bus *bus,
for (i = 0; i < count; i++) {
if (params[i].lane != l)
continue;
+ /* Skip inactive groups to avoid an invalid hstart/hstop range. */
+ if (!params[i].hwidth)
+ continue;
port_bo = 1;
list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
@@ -274,9 +283,13 @@ static int sdw_compute_group_params(struct sdw_bus *bus,
for (i = 0; i < group->count; i++) {
if (params[i].lane != l)
continue;
+ /* Inactive groups must not enter the hwidth calculation. */
+ if (!params[i].payload_bw)
+ continue;
- params[i].hwidth = (sel_col * params[i].payload_bw +
- params[i].full_bw - 1) / params[i].full_bw;
+ params[i].hwidth =
+ sdw_compute_hwidth(sel_col, params[i].payload_bw,
+ params[i].full_bw);
column_needed += params[i].hwidth;
/* There is no control column for lane 1 and above */
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 09/11] soundwire: generic_bandwidth_allocation: export bandwidth helper functions
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
` (7 preceding siblings ...)
2026-11-07 22:08 ` [PATCH 08/11] soundwire: generic_bandwidth_allocation: fix hwidth for skipped block offset Vijendar Mukunda
@ 2026-11-07 22:08 ` Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 10/11] soundwire: amd: add ACP7x generic bandwidth allocation support Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 11/11] soundwire: amd: add interrupt_enabled synchronization for ACP7B/7E/7F Vijendar Mukunda
10 siblings, 0 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:08 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
Export sdw_compute_dp0_port_params(), sdw_compute_port_params(),
sdw_select_row_col(), is_clock_scaling_supported() and
get_manager_lane() so that out-of-tree or modular SoundWire manager
drivers can call them directly. Remove the static qualifier from each
function and add the corresponding EXPORT_SYMBOL() and prototype in
bus.h.
Signed-off-by: Poojitha N <Poojitha.N@amd.com>
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
---
drivers/soundwire/bus.h | 6 ++++++
.../soundwire/generic_bandwidth_allocation.c | 17 +++++++++++------
2 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h
index 44e4f5193917..0c5c343b2c1c 100644
--- a/drivers/soundwire/bus.h
+++ b/drivers/soundwire/bus.h
@@ -241,5 +241,11 @@ void sdw_clear_slave_status(struct sdw_bus *bus, u32 request);
int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size);
void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt,
struct sdw_transport_data *t_data);
+void sdw_compute_dp0_port_params(struct sdw_bus *bus);
+int sdw_compute_port_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream);
+int sdw_select_row_col(struct sdw_bus *bus, int clk_freq);
+bool is_clock_scaling_supported(struct sdw_bus *bus);
+int get_manager_lane(struct sdw_bus *bus, struct sdw_master_runtime *m_rt,
+ struct sdw_slave_runtime *s_rt, unsigned int curr_dr_freq);
#endif /* __SDW_BUS_H */
diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c
index 1dd3b2e5420c..cf2d2275b6aa 100644
--- a/drivers/soundwire/generic_bandwidth_allocation.c
+++ b/drivers/soundwire/generic_bandwidth_allocation.c
@@ -124,7 +124,7 @@ static void sdw_compute_dp0_master_ports(struct sdw_master_runtime *m_rt)
}
}
-static void sdw_compute_dp0_port_params(struct sdw_bus *bus)
+void sdw_compute_dp0_port_params(struct sdw_bus *bus)
{
struct sdw_master_runtime *m_rt;
@@ -136,6 +136,7 @@ static void sdw_compute_dp0_port_params(struct sdw_bus *bus)
sdw_compute_dp0_slave_ports(m_rt);
}
}
+EXPORT_SYMBOL(sdw_compute_dp0_port_params);
static void sdw_compute_master_ports(struct sdw_master_runtime *m_rt,
struct sdw_group_params *params,
@@ -397,7 +398,7 @@ static int sdw_get_group_count(struct sdw_bus *bus,
* @bus: SDW Bus instance
* @stream: Soundwire stream
*/
-static int sdw_compute_port_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream)
+int sdw_compute_port_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream)
{
struct sdw_group_params *params = NULL;
struct sdw_group group;
@@ -431,8 +432,9 @@ static int sdw_compute_port_params(struct sdw_bus *bus, struct sdw_stream_runtim
return ret;
}
+EXPORT_SYMBOL(sdw_compute_port_params);
-static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq)
+int sdw_select_row_col(struct sdw_bus *bus, int clk_freq)
{
struct sdw_master_prop *prop = &bus->prop;
int r, c;
@@ -455,8 +457,9 @@ static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq)
return -EINVAL;
}
+EXPORT_SYMBOL(sdw_select_row_col);
-static bool is_clock_scaling_supported(struct sdw_bus *bus)
+bool is_clock_scaling_supported(struct sdw_bus *bus)
{
struct sdw_master_runtime *m_rt;
struct sdw_slave_runtime *s_rt;
@@ -468,6 +471,7 @@ static bool is_clock_scaling_supported(struct sdw_bus *bus)
return true;
}
+EXPORT_SYMBOL(is_clock_scaling_supported);
/**
* is_lane_connected_to_all_peripherals: Check if the given manager lane connects to all peripherals
@@ -500,8 +504,8 @@ static bool is_lane_connected_to_all_peripherals(struct sdw_master_runtime *m_rt
return true;
}
-static int get_manager_lane(struct sdw_bus *bus, struct sdw_master_runtime *m_rt,
- struct sdw_slave_runtime *s_rt, unsigned int curr_dr_freq)
+int get_manager_lane(struct sdw_bus *bus, struct sdw_master_runtime *m_rt,
+ struct sdw_slave_runtime *s_rt, unsigned int curr_dr_freq)
{
struct sdw_slave_prop *slave_prop = &s_rt->slave->prop;
struct sdw_port_runtime *m_p_rt;
@@ -544,6 +548,7 @@ static int get_manager_lane(struct sdw_bus *bus, struct sdw_master_runtime *m_rt
/* No available multi lane found, only lane 0 can be used */
return 0;
}
+EXPORT_SYMBOL(get_manager_lane);
/**
* sdw_compute_bus_params: Compute bus parameters
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 10/11] soundwire: amd: add ACP7x generic bandwidth allocation support
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
` (8 preceding siblings ...)
2026-11-07 22:08 ` [PATCH 09/11] soundwire: generic_bandwidth_allocation: export bandwidth helper functions Vijendar Mukunda
@ 2026-11-07 22:08 ` Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 11/11] soundwire: amd: add interrupt_enabled synchronization for ACP7B/7E/7F Vijendar Mukunda
10 siblings, 0 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:08 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
Add amd_acp7x_sdw_compute_bus_params() and
amd_acp7x_sdw_compute_params() for ACP7B/7E/7F managers.
The bus parameter computation performs dynamic frame-shape selection
across candidate clock frequencies, using sdw_check_column_allocation()
as a pre-check before accepting a clock rate to avoid configurations
that cannot satisfy all active stream bandwidth constraints.
ACP7x managers register amd_acp7x_sdw_compute_params as their
bus.compute_params callback.
Add and export sdw_check_column_allocation() in
generic_bandwidth_allocation.c, and declare it in bus.h, so this
ACP7x-specific code can call it across the module boundary.
Signed-off-by: Poojitha N <Poojitha.N@amd.com>
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
---
drivers/soundwire/amd_acp7x_manager.c | 191 ++++++++++++++++++
drivers/soundwire/amd_manager.h | 1 +
drivers/soundwire/amd_manager_common.c | 1 +
drivers/soundwire/bus.h | 4 +
.../soundwire/generic_bandwidth_allocation.c | 95 +++++++++
5 files changed, 292 insertions(+)
diff --git a/drivers/soundwire/amd_acp7x_manager.c b/drivers/soundwire/amd_acp7x_manager.c
index 721d4e97371b..9605577a8d35 100644
--- a/drivers/soundwire/amd_acp7x_manager.c
+++ b/drivers/soundwire/amd_acp7x_manager.c
@@ -921,6 +921,197 @@ static int amd_acp7x_sdw_post_bank_switch(struct sdw_bus *bus)
return 0;
}
+/*
+ * Reuses grouping, lane-selection, and transport helpers exported by
+ * the generic bandwidth allocator (see bus.h) for ACP7x bus params.
+ */
+
+/**
+ * amd_acp7x_sdw_compute_bus_params() - Compute bus parameters for AMD ACP7x
+ *
+ * @bus: SDW Bus instance
+ */
+static int amd_acp7x_sdw_compute_bus_params(struct sdw_bus *bus,
+ struct sdw_stream_runtime *stream)
+{
+ struct sdw_master_prop *mstr_prop = &bus->prop;
+ struct sdw_slave_prop *slave_prop;
+ struct sdw_port_runtime *m_p_rt;
+ struct sdw_port_runtime *s_p_rt;
+ struct sdw_master_runtime *m_rt;
+ struct sdw_slave_runtime *s_rt;
+ unsigned int curr_dr_freq = 0;
+ unsigned int old_default_row, old_default_col;
+ unsigned int default_row = 0, default_col = 0;
+ int i, l, clk_values, ret;
+ bool is_gear = false;
+ int m_lane = 0;
+ u32 *clk_buf;
+
+ if (mstr_prop->num_clk_gears) {
+ clk_values = mstr_prop->num_clk_gears;
+ clk_buf = mstr_prop->clk_gears;
+ is_gear = true;
+ } else if (mstr_prop->num_clk_freq) {
+ clk_values = mstr_prop->num_clk_freq;
+ clk_buf = mstr_prop->clk_freq;
+ } else {
+ clk_values = 1;
+ clk_buf = NULL;
+ }
+ if (!is_clock_scaling_supported(bus))
+ clk_values = 1;
+
+ if (!mstr_prop->default_frame_rate ||
+ (!mstr_prop->dynamic_frame &&
+ (!mstr_prop->default_row || !mstr_prop->default_col)))
+ return -EINVAL;
+ old_default_row = mstr_prop->default_row;
+ old_default_col = mstr_prop->default_col;
+
+ for (i = 0; i < clk_values; i++) {
+ if (!clk_buf) {
+ curr_dr_freq = bus->params.max_dr_freq;
+ } else {
+ curr_dr_freq = is_gear ?
+ (bus->params.max_dr_freq / clk_buf[i]) :
+ clk_buf[i] * SDW_DOUBLE_RATE_FACTOR;
+ }
+
+ if (mstr_prop->dynamic_frame) {
+ switch (curr_dr_freq) {
+ case 24000000:
+ default_row = 50;
+ default_col = 10;
+ break;
+ case 12000000:
+ default_row = 125;
+ default_col = 2;
+ break;
+ case 24576000:
+ default_row = 64;
+ default_col = 8;
+ break;
+ case 12288000:
+ default_row = 64;
+ default_col = 4;
+ break;
+ case 6144000:
+ default_row = 64;
+ default_col = 2;
+ break;
+ default:
+ dev_dbg(bus->dev, "unsupported clock frequency %u, skipping\n",
+ curr_dr_freq);
+ continue;
+ }
+ } else {
+ /* Static frame row and column are provided as a pair by ACPI. */
+ default_row = mstr_prop->default_row;
+ default_col = mstr_prop->default_col;
+ }
+ /*
+ * The row and column pair is complete here: dynamic values are
+ * selected by frequency above, while static values come from ACPI.
+ */
+
+ dev_dbg(bus->dev, "Trying clock frequency %d bandwidth:%d default_col:%d\n",
+ curr_dr_freq, bus->params.bandwidth, default_col);
+
+ if (curr_dr_freq * (default_col - 1) >=
+ bus->params.bandwidth * default_col) {
+ ret = sdw_check_column_allocation(bus, stream, curr_dr_freq,
+ default_col);
+ if (ret < 0)
+ return ret;
+ if (ret)
+ break;
+ dev_dbg(bus->dev, "freq %d: column allocation failed\n",
+ curr_dr_freq);
+ }
+ /* Try multi-lane when single-lane at this frequency is insufficient. */
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ /*
+ * Get the first s_rt that will be used to find the available lane that
+ * can be used. No need to check all Peripherals because we can't use
+ * multi-lane if we can't find any available lane for the first Peripheral.
+ */
+ if (list_empty(&m_rt->slave_rt_list))
+ continue;
+
+ s_rt = list_first_entry(&m_rt->slave_rt_list,
+ struct sdw_slave_runtime, m_rt_node);
+ m_lane = get_manager_lane(bus, m_rt, s_rt, curr_dr_freq);
+ if (m_lane > 0)
+ goto out;
+ }
+ }
+ if (i == clk_values) {
+ dev_err(bus->dev, "%s: could not find clock value for bandwidth %d\n",
+ __func__, bus->params.bandwidth);
+ return -EINVAL;
+ }
+out:
+ if (m_lane > 0) {
+ list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
+ slave_prop = &s_rt->slave->prop;
+ for (l = 1; l < SDW_MAX_LANES; l++) {
+ if (slave_prop->lane_maps[l] == m_lane) {
+ list_for_each_entry(s_p_rt, &s_rt->port_list, port_node) {
+ s_p_rt->lane = l;
+ }
+ break;
+ }
+ }
+ }
+ list_for_each_entry(m_p_rt, &m_rt->port_list, port_node)
+ m_p_rt->lane = m_lane;
+ }
+
+ mstr_prop->default_row = default_row;
+ mstr_prop->default_col = default_col;
+ ret = sdw_select_row_col(bus, curr_dr_freq);
+ if (ret < 0) {
+ dev_err(bus->dev, "%s: could not find frame configuration for bus dr_freq %d\n",
+ __func__, curr_dr_freq);
+ mstr_prop->default_row = old_default_row;
+ mstr_prop->default_col = old_default_col;
+ return -EINVAL;
+ }
+ bus->params.curr_dr_freq = curr_dr_freq;
+ return 0;
+}
+
+/**
+ * amd_acp7x_sdw_compute_params() - Compute bus, transport and port parameters
+ * for AMD ACP7x
+ *
+ * @bus: SDW Bus instance
+ * @stream: Soundwire stream
+ */
+int amd_acp7x_sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream)
+{
+ int ret;
+
+ ret = amd_acp7x_sdw_compute_bus_params(bus, stream);
+ if (ret < 0)
+ return ret;
+
+ if (stream->type == SDW_STREAM_BPT) {
+ sdw_compute_dp0_port_params(bus);
+ return 0;
+ }
+
+ ret = sdw_compute_port_params(bus, stream);
+ if (ret < 0) {
+ dev_err(bus->dev, "Compute transport params failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
const struct sdw_master_port_ops amd_acp7x_sdw_port_ops = {
.dpn_set_port_params = amd_acp7x_sdw_port_params,
.dpn_set_port_transport_params = amd_acp7x_sdw_transport_params,
diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h
index f0d3c5684744..c3a7db663772 100644
--- a/drivers/soundwire/amd_manager.h
+++ b/drivers/soundwire/amd_manager.h
@@ -510,6 +510,7 @@ void amd_acp7x_sdw_update_slave_status_work(struct amd_sdw_manager *amd_manager)
int amd_sdw_hw_init_ops_acp7x(struct amd_sdw_manager *amd_manager);
extern const struct sdw_master_ops amd_acp7x_sdw_ops;
extern const struct sdw_master_port_ops amd_acp7x_sdw_port_ops;
+int amd_acp7x_sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream);
#define AMD_SDW_HW_OPS(amd_manager, cb) ((amd_manager)->hw_ops->cb)
diff --git a/drivers/soundwire/amd_manager_common.c b/drivers/soundwire/amd_manager_common.c
index 03c92b6991f2..0920775a8fe1 100644
--- a/drivers/soundwire/amd_manager_common.c
+++ b/drivers/soundwire/amd_manager_common.c
@@ -862,6 +862,7 @@ static int amd_sdw_manager_probe(struct platform_device *pdev)
/* mmio already set in the port-count switch above */
amd_manager->bus.ops = &amd_acp7x_sdw_ops;
amd_manager->bus.port_ops = &amd_acp7x_sdw_port_ops;
+ amd_manager->bus.compute_params = &amd_acp7x_sdw_compute_params;
break;
default:
dev_err(dev, "unsupported ACP revision: 0x%x\n", amd_manager->acp_rev);
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h
index 0c5c343b2c1c..bcc7aa7e3a0e 100644
--- a/drivers/soundwire/bus.h
+++ b/drivers/soundwire/bus.h
@@ -245,6 +245,10 @@ void sdw_compute_dp0_port_params(struct sdw_bus *bus);
int sdw_compute_port_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream);
int sdw_select_row_col(struct sdw_bus *bus, int clk_freq);
bool is_clock_scaling_supported(struct sdw_bus *bus);
+int sdw_check_column_allocation(struct sdw_bus *bus,
+ struct sdw_stream_runtime *stream,
+ unsigned int curr_dr_freq,
+ int sel_col);
int get_manager_lane(struct sdw_bus *bus, struct sdw_master_runtime *m_rt,
struct sdw_slave_runtime *s_rt, unsigned int curr_dr_freq);
diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c
index cf2d2275b6aa..44e4c0f78d9c 100644
--- a/drivers/soundwire/generic_bandwidth_allocation.c
+++ b/drivers/soundwire/generic_bandwidth_allocation.c
@@ -473,6 +473,101 @@ bool is_clock_scaling_supported(struct sdw_bus *bus)
}
EXPORT_SYMBOL(is_clock_scaling_supported);
+/**
+ * sdw_check_column_allocation - Pre-check if column allocation will succeed
+ * @bus: SDW Bus instance
+ * @stream: SoundWire stream being prepared
+ * @curr_dr_freq: Current double-rate frequency to check
+ * @sel_col: Number of columns
+ *
+ * Return: 1 if allocation succeeds, 0 if it does not fit, or a negative error.
+ */
+int sdw_check_column_allocation(struct sdw_bus *bus,
+ struct sdw_stream_runtime *stream,
+ unsigned int curr_dr_freq,
+ int sel_col)
+{
+ struct sdw_master_runtime *m_rt;
+ struct sdw_port_runtime *p_rt;
+ struct sdw_group group = {};
+ unsigned int rate, bps, ch;
+ int *payload_bw = NULL;
+ int *full_bw = NULL;
+ int i, l, ret;
+ int result = 1;
+
+ ret = sdw_get_group_count(bus, &group);
+ if (ret < 0)
+ return ret;
+ if (!group.count)
+ goto out;
+
+ payload_bw = kcalloc(group.count, sizeof(int), GFP_KERNEL);
+ full_bw = kcalloc(group.count, sizeof(int), GFP_KERNEL);
+ if (!payload_bw || !full_bw) {
+ result = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < group.count; i++)
+ full_bw[i] = curr_dr_freq / group.rates[i];
+
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
+ if (m_rt->stream == stream) {
+ if (stream->state != SDW_STREAM_CONFIGURED)
+ continue;
+ } else {
+ if (m_rt->stream->state != SDW_STREAM_ENABLED &&
+ m_rt->stream->state != SDW_STREAM_PREPARED &&
+ m_rt->stream->state != SDW_STREAM_DISABLED)
+ continue;
+ }
+
+ list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
+ rate = m_rt->stream->params.rate;
+ bps = m_rt->stream->params.bps;
+ ch = hweight32(p_rt->ch_mask);
+
+ for (i = 0; i < group.count; i++) {
+ if (rate == group.rates[i] && p_rt->lane == group.lanes[i])
+ payload_bw[i] += bps * ch;
+ }
+ }
+ }
+
+ for (l = 0; l < SDW_MAX_LANES; l++) {
+ int available_columns = l ? sel_col : sel_col - 1;
+ int column_needed = 0;
+
+ for (i = 0; i < group.count; i++) {
+ int hwidth;
+
+ if (group.lanes[i] != l || !payload_bw[i])
+ continue;
+
+ /* Include the column consumed by the skipped block offset. */
+ hwidth = sdw_compute_hwidth(sel_col, payload_bw[i],
+ full_bw[i]);
+ column_needed += hwidth;
+ }
+
+ if (column_needed > available_columns) {
+ dev_dbg(bus->dev, "Lane %d column check failed: required=%d available=%d\n",
+ l, column_needed, available_columns);
+ result = 0;
+ break;
+ }
+ }
+
+out:
+ kfree(payload_bw);
+ kfree(full_bw);
+ kfree(group.rates);
+ kfree(group.lanes);
+ return result;
+}
+EXPORT_SYMBOL(sdw_check_column_allocation);
+
/**
* is_lane_connected_to_all_peripherals: Check if the given manager lane connects to all peripherals
* So that all peripherals can use the manager lane.
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 11/11] soundwire: amd: add interrupt_enabled synchronization for ACP7B/7E/7F
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
` (9 preceding siblings ...)
2026-11-07 22:08 ` [PATCH 10/11] soundwire: amd: add ACP7x generic bandwidth allocation support Vijendar Mukunda
@ 2026-11-07 22:08 ` Vijendar Mukunda
10 siblings, 0 replies; 12+ messages in thread
From: Vijendar Mukunda @ 2026-11-07 22:08 UTC (permalink / raw)
To: vkoul
Cc: broonie, yung-chuan.liao, pierre-louis.bossart,
Basavaraj.Hiregoudar, Sunil-kumar.Dommati, venkataprasad.potturu,
Syed.SabaKareem, Mario.Limonciello, Poojitha.N, sravisar,
Richard.Gong, linux-sound, linux-kernel, Vijendar Mukunda
Mirror the interrupt_enabled guard already used by the ACP6.3/7.0/7.1/7.2
manager to prevent new work from being queued during teardown.
- amd_acp7x_enable_sdw_interrupts(): set interrupt_enabled after all
hardware interrupt mask registers are written.
- amd_acp7x_disable_sdw_interrupts(): clear interrupt_enabled before
writing the hardware mask registers so any in-flight IRQ that arrives
during the mask writes sees the flag as false and skips scheduling.
- amd_acp7x_sdw_irq_thread(): guard schedule_work() with the
interrupt_enabled flag to avoid queueing new work once the driver
has begun teardown.
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
---
drivers/soundwire/amd_acp7x_manager.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/drivers/soundwire/amd_acp7x_manager.c b/drivers/soundwire/amd_acp7x_manager.c
index 9605577a8d35..d48254a279fe 100644
--- a/drivers/soundwire/amd_acp7x_manager.c
+++ b/drivers/soundwire/amd_acp7x_manager.c
@@ -362,12 +362,20 @@ static void amd_acp7x_enable_sdw_interrupts(struct amd_sdw_manager *amd_manager)
writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_MASK_0TO7);
writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio + ACP7X_SW_STATE_CHANGE_STATUS_MASK_8TO11);
writel(AMD_SDW_IRQ_ERROR_MASK, amd_manager->mmio + ACP7X_SW_ERROR_INTR_MASK);
+ amd_manager->interrupt_enabled = true;
}
static void amd_acp7x_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager)
{
u32 val, acp_ext_intr_cntl, sdw_intr_cntl;
+ /*
+ * Clear the flag before updating hardware masks so that any IRQ
+ * that fires during the mask-register writes sees the flag as false
+ * and does not queue new work.
+ */
+ amd_manager->interrupt_enabled = false;
+
mutex_lock(amd_manager->acp_sdw_lock);
val = acp7x_sdw_manager_reg_mask_array[amd_manager->instance];
acp_ext_intr_cntl = readl(amd_manager->acp_mmio + ACP7X_EXTERNAL_INTR_CNTL);
@@ -582,8 +590,9 @@ void amd_acp7x_sdw_irq_thread(struct amd_sdw_manager *amd_manager)
amd_acp7x_sdw_read_and_process_ping_status(amd_manager);
else if (status_change_0to7 || (status_change_8to11 & AMD_SDW_8TO11_DEV_STATUS))
amd_sdw_update_slave_status(status_change_0to7, status_change_8to11, amd_manager);
- if ((status_change_8to11 & AMD_SDW_PREQ_INTR_STAT) ||
- (status_change_8to11 & AMD_SDW_8TO11_DEV_STATUS) || status_change_0to7)
+ if (((status_change_8to11 & AMD_SDW_PREQ_INTR_STAT) ||
+ (status_change_8to11 & AMD_SDW_8TO11_DEV_STATUS) || status_change_0to7) &&
+ amd_manager->interrupt_enabled)
schedule_work(&amd_manager->amd_sdw_work);
}
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-11-07 22:11 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-07 22:07 [PATCH 00/11] soundwire: amd: add ACP7B/7E/7F SoundWire manager support Vijendar Mukunda
2026-11-07 22:07 ` [PATCH 01/11] soundwire: amd: add ACP7B/7E/7F register definitions and revision IDs Vijendar Mukunda
2026-11-07 22:07 ` [PATCH 02/11] soundwire: amd: add ACP7B/7E/7F hardware backend Vijendar Mukunda
2026-11-07 22:07 ` [PATCH 03/11] soundwire: amd: extend ctl_word_prep to set SSP_TAG for ACP7B/7E/7F Vijendar Mukunda
2026-11-07 22:07 ` [PATCH 04/11] soundwire: amd: add ACP7B/7E/7F DAI PCM capabilities in register_dais() Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 05/11] soundwire: amd: enable host wake support for ACP7B/7E/7F Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 06/11] soundwire: amd: add ACP7B/7E/7F clock-stop support Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 07/11] soundwire: amd: add IO pad enable/disable for ACP7B/7E/7F D0/D3 sequence Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 08/11] soundwire: generic_bandwidth_allocation: fix hwidth for skipped block offset Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 09/11] soundwire: generic_bandwidth_allocation: export bandwidth helper functions Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 10/11] soundwire: amd: add ACP7x generic bandwidth allocation support Vijendar Mukunda
2026-11-07 22:08 ` [PATCH 11/11] soundwire: amd: add interrupt_enabled synchronization for ACP7B/7E/7F Vijendar Mukunda
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®