Skip to content

Commit 7389337

Browse files
Loic PoulainMani-Sadhasivam
authored andcommitted
mhi: pci_generic: Add suspend/resume/recovery procedure
Add support for system wide suspend/resume. During suspend, MHI device controller must be put in M3 state and PCI bus in D3 state. Add a recovery procedure allowing to reinitialize the device in case of error during resume steps, which can happen if device loses power (and so its context) while system suspend. Reviewed-by Hemant Kumar <hemantk@codeaurora.org> Signed-off-by: Loic Poulain <loic.poulain@linaro.org> Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
1 parent 8ccc327 commit 7389337

File tree

1 file changed

+105
-0
lines changed

1 file changed

+105
-0
lines changed

drivers/bus/mhi/pci_generic.c

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <linux/mhi.h>
1414
#include <linux/module.h>
1515
#include <linux/pci.h>
16+
#include <linux/workqueue.h>
1617

1718
#define MHI_PCI_DEFAULT_BAR_NUM 0
1819

@@ -186,6 +187,7 @@ enum mhi_pci_device_status {
186187
struct mhi_pci_device {
187188
struct mhi_controller mhi_cntrl;
188189
struct pci_saved_state *pci_state;
190+
struct work_struct recovery_work;
189191
unsigned long status;
190192
};
191193

@@ -313,6 +315,50 @@ static void mhi_pci_runtime_put(struct mhi_controller *mhi_cntrl)
313315
/* no PM for now */
314316
}
315317

318+
static void mhi_pci_recovery_work(struct work_struct *work)
319+
{
320+
struct mhi_pci_device *mhi_pdev = container_of(work, struct mhi_pci_device,
321+
recovery_work);
322+
struct mhi_controller *mhi_cntrl = &mhi_pdev->mhi_cntrl;
323+
struct pci_dev *pdev = to_pci_dev(mhi_cntrl->cntrl_dev);
324+
int err;
325+
326+
dev_warn(&pdev->dev, "device recovery started\n");
327+
328+
/* Clean up MHI state */
329+
if (test_and_clear_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status)) {
330+
mhi_power_down(mhi_cntrl, false);
331+
mhi_unprepare_after_power_down(mhi_cntrl);
332+
}
333+
334+
/* Check if we can recover without full reset */
335+
pci_set_power_state(pdev, PCI_D0);
336+
pci_load_saved_state(pdev, mhi_pdev->pci_state);
337+
pci_restore_state(pdev);
338+
339+
if (!mhi_pci_is_alive(mhi_cntrl))
340+
goto err_try_reset;
341+
342+
err = mhi_prepare_for_power_up(mhi_cntrl);
343+
if (err)
344+
goto err_try_reset;
345+
346+
err = mhi_sync_power_up(mhi_cntrl);
347+
if (err)
348+
goto err_unprepare;
349+
350+
dev_dbg(&pdev->dev, "Recovery completed\n");
351+
352+
set_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status);
353+
return;
354+
355+
err_unprepare:
356+
mhi_unprepare_after_power_down(mhi_cntrl);
357+
err_try_reset:
358+
if (pci_reset_function(pdev))
359+
dev_err(&pdev->dev, "Recovery failed\n");
360+
}
361+
316362
static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
317363
{
318364
const struct mhi_pci_dev_info *info = (struct mhi_pci_dev_info *) id->driver_data;
@@ -328,6 +374,8 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
328374
if (!mhi_pdev)
329375
return -ENOMEM;
330376

377+
INIT_WORK(&mhi_pdev->recovery_work, mhi_pci_recovery_work);
378+
331379
mhi_cntrl_config = info->config;
332380
mhi_cntrl = &mhi_pdev->mhi_cntrl;
333381

@@ -391,6 +439,8 @@ static void mhi_pci_remove(struct pci_dev *pdev)
391439
struct mhi_pci_device *mhi_pdev = pci_get_drvdata(pdev);
392440
struct mhi_controller *mhi_cntrl = &mhi_pdev->mhi_cntrl;
393441

442+
cancel_work_sync(&mhi_pdev->recovery_work);
443+
394444
if (test_and_clear_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status)) {
395445
mhi_power_down(mhi_cntrl, true);
396446
mhi_unprepare_after_power_down(mhi_cntrl);
@@ -456,12 +506,67 @@ static const struct pci_error_handlers mhi_pci_err_handler = {
456506
.reset_done = mhi_pci_reset_done,
457507
};
458508

509+
static int __maybe_unused mhi_pci_suspend(struct device *dev)
510+
{
511+
struct pci_dev *pdev = to_pci_dev(dev);
512+
struct mhi_pci_device *mhi_pdev = dev_get_drvdata(dev);
513+
struct mhi_controller *mhi_cntrl = &mhi_pdev->mhi_cntrl;
514+
515+
cancel_work_sync(&mhi_pdev->recovery_work);
516+
517+
/* Transition to M3 state */
518+
mhi_pm_suspend(mhi_cntrl);
519+
520+
pci_save_state(pdev);
521+
pci_disable_device(pdev);
522+
pci_wake_from_d3(pdev, true);
523+
pci_set_power_state(pdev, PCI_D3hot);
524+
525+
return 0;
526+
}
527+
528+
static int __maybe_unused mhi_pci_resume(struct device *dev)
529+
{
530+
struct pci_dev *pdev = to_pci_dev(dev);
531+
struct mhi_pci_device *mhi_pdev = dev_get_drvdata(dev);
532+
struct mhi_controller *mhi_cntrl = &mhi_pdev->mhi_cntrl;
533+
int err;
534+
535+
pci_set_power_state(pdev, PCI_D0);
536+
pci_restore_state(pdev);
537+
pci_set_master(pdev);
538+
539+
err = pci_enable_device(pdev);
540+
if (err)
541+
goto err_recovery;
542+
543+
/* Exit M3, transition to M0 state */
544+
err = mhi_pm_resume(mhi_cntrl);
545+
if (err) {
546+
dev_err(&pdev->dev, "failed to resume device: %d\n", err);
547+
goto err_recovery;
548+
}
549+
550+
return 0;
551+
552+
err_recovery:
553+
/* The device may have loose power or crashed, try recovering it */
554+
queue_work(system_long_wq, &mhi_pdev->recovery_work);
555+
556+
return err;
557+
}
558+
559+
static const struct dev_pm_ops mhi_pci_pm_ops = {
560+
SET_SYSTEM_SLEEP_PM_OPS(mhi_pci_suspend, mhi_pci_resume)
561+
};
562+
459563
static struct pci_driver mhi_pci_driver = {
460564
.name = "mhi-pci-generic",
461565
.id_table = mhi_pci_id_table,
462566
.probe = mhi_pci_probe,
463567
.remove = mhi_pci_remove,
464568
.err_handler = &mhi_pci_err_handler,
569+
.driver.pm = &mhi_pci_pm_ops
465570
};
466571
module_pci_driver(mhi_pci_driver);
467572

0 commit comments

Comments
 (0)