Skip to content

Commit 029bfd1

Browse files
plbossartvinodkoul
authored andcommitted
soundwire: intel: conditionally exit clock stop mode on system suspend
Intel stress tests reported issues with the clock stop mode, specifically when trying to do a system suspend while the link is already pm_runtime suspended. In this case, we need to disable the shim wake, but when the PCI parent device is also pm_runtime suspended the SHIM registers are not accessible. Since this is an invalid corner case, this patch suggests a pm_runtime resume of the entire bus to full power (parent+child devices) before the system suspend so that the shim wake can be disabled. Unlike the suspend operation, the .prepare callbacks are propagated from root device to leaf devices. By adding a .prepare callback at the SoundWire link level, we can double-check the pm_runtime status of the device as well as its parent PCI device. When the problematic configuration is detected, the device is pm_runtime resumed - which by construction also resume its parent. An additional loop is added to resume all child devices. In theory we only need to restart the link, but doing so will also cause the physical devices to synchronize and re-initialize, while their Linux devices remain pm_runtime suspended. It's simpler to make sure the codec devices are fully resumed so that we don't have to deal with zombie states. This additional loop could have been avoided by adding a .prepare callback in SoundWire codec drivers. Functionally this would have been equivalent. The rationale for implementing a loop at the link level is only to reduce the amount of code required to deal at the codec level with an Intel corner case - in other words keep codec drivers independent from Intel platform-specific programming sequences. BugLink: thesofproject#2606 Signed-off-by: Pierre-Louis Bossart <[email protected]> Signed-off-by: Bard Liao <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Vinod Koul <[email protected]>
1 parent e4401ab commit 029bfd1

File tree

1 file changed

+94
-13
lines changed

1 file changed

+94
-13
lines changed

drivers/soundwire/intel.c

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,87 @@ int intel_link_process_wakeen_event(struct auxiliary_device *auxdev)
15961596
* PM calls
15971597
*/
15981598

1599+
static int intel_resume_child_device(struct device *dev, void *data)
1600+
{
1601+
int ret;
1602+
struct sdw_slave *slave = dev_to_sdw_dev(dev);
1603+
1604+
if (!slave->probed) {
1605+
dev_dbg(dev, "%s: skipping device, no probed driver\n", __func__);
1606+
return 0;
1607+
}
1608+
if (!slave->dev_num_sticky) {
1609+
dev_dbg(dev, "%s: skipping device, never detected on bus\n", __func__);
1610+
return 0;
1611+
}
1612+
1613+
ret = pm_request_resume(dev);
1614+
if (ret < 0)
1615+
dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
1616+
1617+
return ret;
1618+
}
1619+
1620+
static int __maybe_unused intel_pm_prepare(struct device *dev)
1621+
{
1622+
struct sdw_cdns *cdns = dev_get_drvdata(dev);
1623+
struct sdw_intel *sdw = cdns_to_intel(cdns);
1624+
struct sdw_bus *bus = &cdns->bus;
1625+
u32 clock_stop_quirks;
1626+
int ret = 0;
1627+
1628+
if (bus->prop.hw_disabled || !sdw->startup_done) {
1629+
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
1630+
bus->link_id);
1631+
return 0;
1632+
}
1633+
1634+
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
1635+
1636+
if (pm_runtime_suspended(dev) &&
1637+
pm_runtime_suspended(dev->parent) &&
1638+
((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
1639+
!clock_stop_quirks)) {
1640+
/*
1641+
* if we've enabled clock stop, and the parent is suspended, the SHIM registers
1642+
* are not accessible and the shim wake cannot be disabled.
1643+
* The only solution is to resume the entire bus to full power
1644+
*/
1645+
1646+
/*
1647+
* If any operation in this block fails, we keep going since we don't want
1648+
* to prevent system suspend from happening and errors should be recoverable
1649+
* on resume.
1650+
*/
1651+
1652+
/*
1653+
* first resume the device for this link. This will also by construction
1654+
* resume the PCI parent device.
1655+
*/
1656+
ret = pm_request_resume(dev);
1657+
if (ret < 0) {
1658+
dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
1659+
return 0;
1660+
}
1661+
1662+
/*
1663+
* Continue resuming the entire bus (parent + child devices) to exit
1664+
* the clock stop mode. If there are no devices connected on this link
1665+
* this is a no-op.
1666+
* The resume to full power could have been implemented with a .prepare
1667+
* step in SoundWire codec drivers. This would however require a lot
1668+
* of code to handle an Intel-specific corner case. It is simpler in
1669+
* practice to add a loop at the link level.
1670+
*/
1671+
ret = device_for_each_child(bus->dev, NULL, intel_resume_child_device);
1672+
1673+
if (ret < 0)
1674+
dev_err(dev, "%s: intel_resume_child_device failed: %d\n", __func__, ret);
1675+
}
1676+
1677+
return 0;
1678+
}
1679+
15991680
static int __maybe_unused intel_suspend(struct device *dev)
16001681
{
16011682
struct sdw_cdns *cdns = dev_get_drvdata(dev);
@@ -1615,19 +1696,18 @@ static int __maybe_unused intel_suspend(struct device *dev)
16151696

16161697
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
16171698

1618-
if ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET ||
1619-
!clock_stop_quirks) &&
1620-
!pm_runtime_suspended(dev->parent)) {
1621-
1622-
/*
1623-
* if we've enabled clock stop, and the parent
1624-
* is still active, disable shim wake. The
1625-
* SHIM registers are not accessible if the
1626-
* parent is already pm_runtime suspended so
1627-
* it's too late to change that configuration
1628-
*/
1629-
1630-
intel_shim_wake(sdw, false);
1699+
if ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
1700+
!clock_stop_quirks) {
1701+
1702+
if (pm_runtime_suspended(dev->parent)) {
1703+
/*
1704+
* paranoia check: this should not happen with the .prepare
1705+
* resume to full power
1706+
*/
1707+
dev_err(dev, "%s: invalid config: parent is suspended\n", __func__);
1708+
} else {
1709+
intel_shim_wake(sdw, false);
1710+
}
16311711
}
16321712

16331713
return 0;
@@ -1992,6 +2072,7 @@ static int __maybe_unused intel_resume_runtime(struct device *dev)
19922072
}
19932073

19942074
static const struct dev_pm_ops intel_pm = {
2075+
.prepare = intel_pm_prepare,
19952076
SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume)
19962077
SET_RUNTIME_PM_OPS(intel_suspend_runtime, intel_resume_runtime, NULL)
19972078
};

0 commit comments

Comments
 (0)