summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoic Poulain <loic.poulain@linaro.org>2021-02-25 19:01:49 +0100
committerLoic Poulain <loic.poulain@linaro.org>2021-02-26 11:49:39 +0100
commitf48933e2c8a6c06910def81f1ac7323ca6250b67 (patch)
treee19e2bdae5206caa63ac08b4026c48b7d434904f
parent0c2d71a30ff9bc681b1eacb8a67178be2119fab7 (diff)
mhi: pci_generic: Add support for runtime PM5.11.0-quectel-runtime-pm
When the device is idle it is possible to move it into the lowest MHI PM state M3. In that mode MHI operations are suspended and the PCI device can be safely put into PCI D3 state. The device is then resumed from D3/M3 either because of host initiated MHI operation (e.g. buffer TX) or because the device (modem) has triggered wake-up via PME feature (e.g. on incoming data). System wide suspend/resume and runtime suspend/resume procedure are equivalent. Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
-rw-r--r--drivers/bus/mhi/pci_generic.c72
1 files changed, 63 insertions, 9 deletions
diff --git a/drivers/bus/mhi/pci_generic.c b/drivers/bus/mhi/pci_generic.c
index d4e8c9c40572..abf60444bd60 100644
--- a/drivers/bus/mhi/pci_generic.c
+++ b/drivers/bus/mhi/pci_generic.c
@@ -14,6 +14,7 @@
#include <linux/mhi.h>
#include <linux/module.h>
#include <linux/pci.h>
+#include <linux/pm_runtime.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
@@ -341,6 +342,10 @@ static void mhi_pci_status_cb(struct mhi_controller *mhi_cntrl,
case MHI_CB_FATAL_ERROR:
case MHI_CB_SYS_ERROR:
dev_warn(&pdev->dev, "firmware crashed (%u)\n", cb);
+ pm_runtime_forbid(&pdev->dev);
+ break;
+ case MHI_CB_EE_MISSION_MODE:
+ pm_runtime_allow(&pdev->dev);
break;
default:
break;
@@ -462,13 +467,19 @@ static int mhi_pci_get_irqs(struct mhi_controller *mhi_cntrl,
static int mhi_pci_runtime_get(struct mhi_controller *mhi_cntrl)
{
- /* no PM for now */
- return 0;
+ /* The runtime_get() MHI callback means:
+ * Do whatever is requested to leave M3.
+ */
+ return pm_runtime_get(mhi_cntrl->cntrl_dev);
}
static void mhi_pci_runtime_put(struct mhi_controller *mhi_cntrl)
{
- /* no PM for now */
+ /* The runtime_put() MHI callback means:
+ * Device can be moved in M3 state.
+ */
+ pm_runtime_mark_last_busy(mhi_cntrl->cntrl_dev);
+ pm_runtime_put(mhi_cntrl->cntrl_dev);
}
static void mhi_pci_recovery_work(struct work_struct *work)
@@ -607,6 +618,14 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
/* start health check */
mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD);
+ /* Only allow runtime-suspend if PME capable (for wakeup) */
+ if (pci_pme_capable(pdev, PCI_D3hot)) {
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_mark_last_busy(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ }
+
return 0;
err_unprepare:
@@ -630,6 +649,10 @@ static void mhi_pci_remove(struct pci_dev *pdev)
mhi_unprepare_after_power_down(mhi_cntrl);
}
+ /* balancing probe put_noidle */
+ if (pci_pme_capable(pdev, PCI_D3hot))
+ pm_runtime_get_noresume(&pdev->dev);
+
mhi_unregister_controller(mhi_cntrl);
}
@@ -740,27 +763,49 @@ static const struct pci_error_handlers mhi_pci_err_handler = {
.reset_done = mhi_pci_reset_done,
};
-static int __maybe_unused mhi_pci_suspend(struct device *dev)
+static int __maybe_unused mhi_pci_runtime_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct mhi_pci_device *mhi_pdev = dev_get_drvdata(dev);
struct mhi_controller *mhi_cntrl = &mhi_pdev->mhi_cntrl;
+ int err;
+
+ if (!test_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status) ||
+ mhi_cntrl->ee != MHI_EE_AMSS)
+ goto pci_suspend; /* Nothing to do at MHI level */
- del_timer(&mhi_pdev->health_check_timer);
cancel_work_sync(&mhi_pdev->recovery_work);
/* Transition to M3 state */
- mhi_pm_suspend(mhi_cntrl);
+ err = mhi_pm_suspend(mhi_cntrl);
+ if (err) {
+ dev_err(&pdev->dev, "failed to suspend device: %d\n", err);
+ return -EBUSY;
+ }
+
+ del_timer(&mhi_pdev->health_check_timer);
+pci_suspend:
pci_save_state(pdev);
pci_disable_device(pdev);
- pci_wake_from_d3(pdev, true);
+ if (pci_wake_from_d3(pdev, true)) {
+ /* In case of PCIe, wakeup does not only enable remote wake-up
+ * for system wide sleep case (Wake on WAN), but also enable
+ * PCIe PME which is used by the device to exit D3 in
+ * runtime-PM case. Without this enabled (PME), device will be
+ * unable to wakeup the host on incoming data (device initiated
+ * resume), causing network latency issues, commands timeouts,
+ * etc. We can not 'force' wake-up enabling from the driver
+ * since it's a user/distro policy, but we can warn...
+ */
+ dev_warn(&pdev->dev, "suspending without wake_up enabled\n");
+ }
pci_set_power_state(pdev, PCI_D3hot);
return 0;
}
-static int __maybe_unused mhi_pci_resume(struct device *dev)
+static int __maybe_unused mhi_pci_runtime_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct mhi_pci_device *mhi_pdev = dev_get_drvdata(dev);
@@ -775,6 +820,13 @@ static int __maybe_unused mhi_pci_resume(struct device *dev)
if (err)
goto err_recovery;
+ /* It can be a remote wakeup (no mhi runtime_get), update access time */
+ pm_runtime_mark_last_busy(dev);
+
+ if (!test_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status) ||
+ mhi_cntrl->ee != MHI_EE_AMSS)
+ return 0; /* Nothing to do at MHI level */
+
/* Exit M3, transition to M0 state */
err = mhi_pm_resume(mhi_cntrl);
if (err) {
@@ -795,7 +847,9 @@ err_recovery:
}
static const struct dev_pm_ops mhi_pci_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(mhi_pci_suspend, mhi_pci_resume)
+ SET_RUNTIME_PM_OPS(mhi_pci_runtime_suspend, mhi_pci_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
};
static struct pci_driver mhi_pci_driver = {