mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [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®