summaryrefslogtreecommitdiff
path: root/drivers/soundwire/intel.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soundwire/intel.c')
-rw-r--r--drivers/soundwire/intel.c166
1 files changed, 133 insertions, 33 deletions
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c
index c11e3d8cd308..f66fcbc33a2f 100644
--- a/drivers/soundwire/intel.c
+++ b/drivers/soundwire/intel.c
@@ -23,6 +23,7 @@
#include "intel.h"
#define INTEL_MASTER_SUSPEND_DELAY_MS 3000
+#define INTEL_MASTER_RESET_ITERATIONS 10
/*
* debug/config flags for the Intel SoundWire Master.
@@ -537,12 +538,14 @@ static int intel_link_power_down(struct sdw_intel *sdw)
mutex_lock(sdw->link_res->shim_lock);
- intel_shim_master_ip_to_glue(sdw);
-
if (!(*shim_mask & BIT(link_id)))
dev_err(sdw->cdns.dev,
"%s: Unbalanced power-up/down calls\n", __func__);
+ sdw->cdns.link_up = false;
+
+ intel_shim_master_ip_to_glue(sdw);
+
*shim_mask &= ~BIT(link_id);
if (!*shim_mask) {
@@ -559,18 +562,19 @@ static int intel_link_power_down(struct sdw_intel *sdw)
link_control &= spa_mask;
ret = intel_clear_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask);
+ if (ret < 0) {
+ dev_err(sdw->cdns.dev, "%s: could not power down link\n", __func__);
+
+ /*
+ * we leave the sdw->cdns.link_up flag as false since we've disabled
+ * the link at this point and cannot handle interrupts any longer.
+ */
+ }
}
mutex_unlock(sdw->link_res->shim_lock);
- if (ret < 0) {
- dev_err(sdw->cdns.dev, "%s: could not power down link\n", __func__);
-
- return ret;
- }
-
- sdw->cdns.link_up = false;
- return 0;
+ return ret;
}
static void intel_shim_sync_arm(struct sdw_intel *sdw)
@@ -1467,6 +1471,8 @@ int intel_link_startup(struct auxiliary_device *auxdev)
goto err_interrupt;
}
}
+ sdw_cdns_check_self_clearing_bits(cdns, __func__,
+ true, INTEL_MASTER_RESET_ITERATIONS);
/* Register DAIs */
ret = intel_register_dai(sdw);
@@ -1519,6 +1525,7 @@ int intel_link_startup(struct auxiliary_device *auxdev)
if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE))
pm_runtime_idle(dev);
+ sdw->startup_done = true;
return 0;
err_interrupt:
@@ -1558,8 +1565,9 @@ int intel_link_process_wakeen_event(struct auxiliary_device *auxdev)
sdw = dev_get_drvdata(dev);
bus = &sdw->cdns.bus;
- if (bus->prop.hw_disabled) {
- dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", bus->link_id);
+ if (bus->prop.hw_disabled || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
+ bus->link_id);
return 0;
}
@@ -1588,6 +1596,87 @@ int intel_link_process_wakeen_event(struct auxiliary_device *auxdev)
* PM calls
*/
+static int intel_resume_child_device(struct device *dev, void *data)
+{
+ int ret;
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+
+ if (!slave->probed) {
+ dev_dbg(dev, "%s: skipping device, no probed driver\n", __func__);
+ return 0;
+ }
+ if (!slave->dev_num_sticky) {
+ dev_dbg(dev, "%s: skipping device, never detected on bus\n", __func__);
+ return 0;
+ }
+
+ ret = pm_request_resume(dev);
+ if (ret < 0)
+ dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
+
+ return ret;
+}
+
+static int __maybe_unused intel_pm_prepare(struct device *dev)
+{
+ struct sdw_cdns *cdns = dev_get_drvdata(dev);
+ struct sdw_intel *sdw = cdns_to_intel(cdns);
+ struct sdw_bus *bus = &cdns->bus;
+ u32 clock_stop_quirks;
+ int ret = 0;
+
+ if (bus->prop.hw_disabled || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
+ bus->link_id);
+ return 0;
+ }
+
+ clock_stop_quirks = sdw->link_res->clock_stop_quirks;
+
+ if (pm_runtime_suspended(dev) &&
+ pm_runtime_suspended(dev->parent) &&
+ ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
+ !clock_stop_quirks)) {
+ /*
+ * if we've enabled clock stop, and the parent is suspended, the SHIM registers
+ * are not accessible and the shim wake cannot be disabled.
+ * The only solution is to resume the entire bus to full power
+ */
+
+ /*
+ * If any operation in this block fails, we keep going since we don't want
+ * to prevent system suspend from happening and errors should be recoverable
+ * on resume.
+ */
+
+ /*
+ * first resume the device for this link. This will also by construction
+ * resume the PCI parent device.
+ */
+ ret = pm_request_resume(dev);
+ if (ret < 0) {
+ dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
+ return 0;
+ }
+
+ /*
+ * Continue resuming the entire bus (parent + child devices) to exit
+ * the clock stop mode. If there are no devices connected on this link
+ * this is a no-op.
+ * The resume to full power could have been implemented with a .prepare
+ * step in SoundWire codec drivers. This would however require a lot
+ * of code to handle an Intel-specific corner case. It is simpler in
+ * practice to add a loop at the link level.
+ */
+ ret = device_for_each_child(bus->dev, NULL, intel_resume_child_device);
+
+ if (ret < 0)
+ dev_err(dev, "%s: intel_resume_child_device failed: %d\n", __func__, ret);
+ }
+
+ return 0;
+}
+
static int __maybe_unused intel_suspend(struct device *dev)
{
struct sdw_cdns *cdns = dev_get_drvdata(dev);
@@ -1596,8 +1685,8 @@ static int __maybe_unused intel_suspend(struct device *dev)
u32 clock_stop_quirks;
int ret;
- if (bus->prop.hw_disabled) {
- dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n",
+ if (bus->prop.hw_disabled || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
}
@@ -1607,19 +1696,18 @@ static int __maybe_unused intel_suspend(struct device *dev)
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
- if ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET ||
- !clock_stop_quirks) &&
- !pm_runtime_suspended(dev->parent)) {
-
- /*
- * if we've enabled clock stop, and the parent
- * is still active, disable shim wake. The
- * SHIM registers are not accessible if the
- * parent is already pm_runtime suspended so
- * it's too late to change that configuration
- */
-
- intel_shim_wake(sdw, false);
+ if ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
+ !clock_stop_quirks) {
+
+ if (pm_runtime_suspended(dev->parent)) {
+ /*
+ * paranoia check: this should not happen with the .prepare
+ * resume to full power
+ */
+ dev_err(dev, "%s: invalid config: parent is suspended\n", __func__);
+ } else {
+ intel_shim_wake(sdw, false);
+ }
}
return 0;
@@ -1650,8 +1738,8 @@ static int __maybe_unused intel_suspend_runtime(struct device *dev)
u32 clock_stop_quirks;
int ret;
- if (bus->prop.hw_disabled) {
- dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n",
+ if (bus->prop.hw_disabled || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
}
@@ -1715,8 +1803,8 @@ static int __maybe_unused intel_resume(struct device *dev)
bool multi_link;
int ret;
- if (bus->prop.hw_disabled) {
- dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n",
+ if (bus->prop.hw_disabled || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
}
@@ -1783,6 +1871,8 @@ static int __maybe_unused intel_resume(struct device *dev)
return ret;
}
}
+ sdw_cdns_check_self_clearing_bits(cdns, __func__,
+ true, INTEL_MASTER_RESET_ITERATIONS);
/*
* after system resume, the pm_runtime suspend() may kick in
@@ -1811,8 +1901,8 @@ static int __maybe_unused intel_resume_runtime(struct device *dev)
int status;
int ret;
- if (bus->prop.hw_disabled) {
- dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n",
+ if (bus->prop.hw_disabled || !sdw->startup_done) {
+ dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
}
@@ -1867,6 +1957,9 @@ static int __maybe_unused intel_resume_runtime(struct device *dev)
return ret;
}
}
+ sdw_cdns_check_self_clearing_bits(cdns, "intel_resume_runtime TEARDOWN",
+ true, INTEL_MASTER_RESET_ITERATIONS);
+
} else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) {
ret = intel_init(sdw);
if (ret) {
@@ -1940,6 +2033,9 @@ static int __maybe_unused intel_resume_runtime(struct device *dev)
}
}
}
+ sdw_cdns_check_self_clearing_bits(cdns, "intel_resume_runtime BUS_RESET",
+ true, INTEL_MASTER_RESET_ITERATIONS);
+
} else if (!clock_stop_quirks) {
clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns);
@@ -1963,6 +2059,9 @@ static int __maybe_unused intel_resume_runtime(struct device *dev)
dev_err(dev, "unable to resume master during resume\n");
return ret;
}
+
+ sdw_cdns_check_self_clearing_bits(cdns, "intel_resume_runtime no_quirks",
+ true, INTEL_MASTER_RESET_ITERATIONS);
} else {
dev_err(dev, "%s clock_stop_quirks %x unsupported\n",
__func__, clock_stop_quirks);
@@ -1973,6 +2072,7 @@ static int __maybe_unused intel_resume_runtime(struct device *dev)
}
static const struct dev_pm_ops intel_pm = {
+ .prepare = intel_pm_prepare,
SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume)
SET_RUNTIME_PM_OPS(intel_suspend_runtime, intel_resume_runtime, NULL)
};