diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 1527b801f013..f2e04a048ac2 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -1079,6 +1079,37 @@ nouveau_pmops_resume(struct device *dev) return ret; } +static void +nouveau_drm_shutdown(struct pci_dev *pdev) +{ + struct nouveau_drm *drm = pci_get_drvdata(pdev); + int ret; + + if (!drm) + return; + + if (drm->dev->switch_power_state == DRM_SWITCH_POWER_OFF || + drm->dev->switch_power_state == DRM_SWITCH_POWER_DYNAMIC_OFF) + return; + + ret = nouveau_do_suspend(drm, false); + if (ret) + NV_ERROR(drm, "shutdown suspend failed with: %d\n", ret); + + pci_save_state(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, PCI_D3hot); + /* + * This is just to give the pci power transition time to settle + * before an immediate kexec jump. it’s mirroring the existing + * nouveau_pmops_suspend() behavior, which already does + * udelay(200) right after pci_set_power_state(..., pci_d3hot). In + * ->shutdown() we’re allowed to sleep, so I used usleep_range() + * instead of a busy-wait udelay(). + */ + usleep_range(200, 400); +} + static int nouveau_pmops_freeze(struct device *dev) { @@ -1408,6 +1439,7 @@ nouveau_drm_pci_driver = { .id_table = nouveau_drm_pci_table, .probe = nouveau_drm_probe, .remove = nouveau_drm_remove, + .shutdown = nouveau_drm_shutdown, .driver.pm = &nouveau_pm_ops, };