diff options
Diffstat (limited to 'drivers/soundwire/intel.c')
-rw-r--r-- | drivers/soundwire/intel.c | 166 |
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) }; |