Commit 7428b2e5 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'drm-fixes-2018-09-12' of git://anongit.freedesktop.org/drm/drm

Pull drm nouveau fixes from Dave Airlie:
 "I'm sending this separately as it's a bit larger than I generally like
  for one driver, but it does contain a bunch of make my nvidia laptop
  not die (runpm) and a bunch to make my docking station and monitor
  display stuff (mst) fixes.

  Lyude has spent a lot of time on these, and we are putting the fixes
  into distro kernels as well asap, as it helps a bunch of standard
  Lenovo laptops, so I'm fairly happy things are better than they were
  before these patches, but I decided to split them out just for
  clarification"

* tag 'drm-fixes-2018-09-12' of git://anongit.freedesktop.org/drm/drm:
  drm/nouveau/disp/gm200-: enforce identity-mapped SOR assignment for LVDS/eDP panels
  drm/nouveau/disp: fix DP disable race
  drm/nouveau/disp: move eDP panel power handling
  drm/nouveau/disp: remove unused struct member
  drm/nouveau/TBDdevinit: don't fail when PMU/PRE_OS is missing from VBIOS
  drm/nouveau/mmu: don't attempt to dereference vmm without valid instance pointer
  drm/nouveau: fix oops in client init failure path
  drm/nouveau: Fix nouveau_connector_ddc_detect()
  drm/nouveau/drm/nouveau: Don't forget to cancel hpd_work on suspend/unload
  drm/nouveau/drm/nouveau: Prevent handling ACPI HPD events too early
  drm/nouveau: Reset MST branching unit before enabling
  drm/nouveau: Only write DP_MSTM_CTRL when needed
  drm/nouveau: Remove useless poll_enable() call in drm_load()
  drm/nouveau: Remove useless poll_disable() call in switcheroo_set_state()
  drm/nouveau: Remove useless poll_enable() call in switcheroo_set_state()
  drm/nouveau: Fix deadlocks in nouveau_connector_detect()
  drm/nouveau/drm/nouveau: Use pm_runtime_get_noresume() in connector_detect()
  drm/nouveau/drm/nouveau: Fix deadlock with fb_helper with async RPM requests
  drm/nouveau: Remove duplicate poll_enable() in pmops_runtime_suspend()
  drm/nouveau/drm/nouveau: Fix bogus drm_kms_helper_poll_enable() placement
parents 67b07609 2887e5ce
...@@ -1123,17 +1123,21 @@ nv50_mstm_enable(struct nv50_mstm *mstm, u8 dpcd, int state) ...@@ -1123,17 +1123,21 @@ nv50_mstm_enable(struct nv50_mstm *mstm, u8 dpcd, int state)
int ret; int ret;
if (dpcd >= 0x12) { if (dpcd >= 0x12) {
ret = drm_dp_dpcd_readb(mstm->mgr.aux, DP_MSTM_CTRL, &dpcd); /* Even if we're enabling MST, start with disabling the
* branching unit to clear any sink-side MST topology state
* that wasn't set by us
*/
ret = drm_dp_dpcd_writeb(mstm->mgr.aux, DP_MSTM_CTRL, 0);
if (ret < 0) if (ret < 0)
return ret; return ret;
dpcd &= ~DP_MST_EN; if (state) {
if (state) /* Now, start initializing */
dpcd |= DP_MST_EN; ret = drm_dp_dpcd_writeb(mstm->mgr.aux, DP_MSTM_CTRL,
DP_MST_EN);
ret = drm_dp_dpcd_writeb(mstm->mgr.aux, DP_MSTM_CTRL, dpcd); if (ret < 0)
if (ret < 0) return ret;
return ret; }
} }
return nvif_mthd(disp, 0, &args, sizeof(args)); return nvif_mthd(disp, 0, &args, sizeof(args));
...@@ -1142,31 +1146,58 @@ nv50_mstm_enable(struct nv50_mstm *mstm, u8 dpcd, int state) ...@@ -1142,31 +1146,58 @@ nv50_mstm_enable(struct nv50_mstm *mstm, u8 dpcd, int state)
int int
nv50_mstm_detect(struct nv50_mstm *mstm, u8 dpcd[8], int allow) nv50_mstm_detect(struct nv50_mstm *mstm, u8 dpcd[8], int allow)
{ {
int ret, state = 0; struct drm_dp_aux *aux;
int ret;
bool old_state, new_state;
u8 mstm_ctrl;
if (!mstm) if (!mstm)
return 0; return 0;
if (dpcd[0] >= 0x12) { mutex_lock(&mstm->mgr.lock);
ret = drm_dp_dpcd_readb(mstm->mgr.aux, DP_MSTM_CAP, &dpcd[1]);
old_state = mstm->mgr.mst_state;
new_state = old_state;
aux = mstm->mgr.aux;
if (old_state) {
/* Just check that the MST hub is still as we expect it */
ret = drm_dp_dpcd_readb(aux, DP_MSTM_CTRL, &mstm_ctrl);
if (ret < 0 || !(mstm_ctrl & DP_MST_EN)) {
DRM_DEBUG_KMS("Hub gone, disabling MST topology\n");
new_state = false;
}
} else if (dpcd[0] >= 0x12) {
ret = drm_dp_dpcd_readb(aux, DP_MSTM_CAP, &dpcd[1]);
if (ret < 0) if (ret < 0)
return ret; goto probe_error;
if (!(dpcd[1] & DP_MST_CAP)) if (!(dpcd[1] & DP_MST_CAP))
dpcd[0] = 0x11; dpcd[0] = 0x11;
else else
state = allow; new_state = allow;
}
if (new_state == old_state) {
mutex_unlock(&mstm->mgr.lock);
return new_state;
} }
ret = nv50_mstm_enable(mstm, dpcd[0], state); ret = nv50_mstm_enable(mstm, dpcd[0], new_state);
if (ret) if (ret)
return ret; goto probe_error;
mutex_unlock(&mstm->mgr.lock);
ret = drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, state); ret = drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, new_state);
if (ret) if (ret)
return nv50_mstm_enable(mstm, dpcd[0], 0); return nv50_mstm_enable(mstm, dpcd[0], 0);
return mstm->mgr.mst_state; return new_state;
probe_error:
mutex_unlock(&mstm->mgr.lock);
return ret;
} }
static void static void
...@@ -2074,7 +2105,7 @@ nv50_disp_atomic_state_alloc(struct drm_device *dev) ...@@ -2074,7 +2105,7 @@ nv50_disp_atomic_state_alloc(struct drm_device *dev)
static const struct drm_mode_config_funcs static const struct drm_mode_config_funcs
nv50_disp_func = { nv50_disp_func = {
.fb_create = nouveau_user_framebuffer_create, .fb_create = nouveau_user_framebuffer_create,
.output_poll_changed = drm_fb_helper_output_poll_changed, .output_poll_changed = nouveau_fbcon_output_poll_changed,
.atomic_check = nv50_disp_atomic_check, .atomic_check = nv50_disp_atomic_check,
.atomic_commit = nv50_disp_atomic_commit, .atomic_commit = nv50_disp_atomic_commit,
.atomic_state_alloc = nv50_disp_atomic_state_alloc, .atomic_state_alloc = nv50_disp_atomic_state_alloc,
......
...@@ -409,59 +409,45 @@ static struct nouveau_encoder * ...@@ -409,59 +409,45 @@ static struct nouveau_encoder *
nouveau_connector_ddc_detect(struct drm_connector *connector) nouveau_connector_ddc_detect(struct drm_connector *connector)
{ {
struct drm_device *dev = connector->dev; struct drm_device *dev = connector->dev;
struct nouveau_connector *nv_connector = nouveau_connector(connector); struct nouveau_encoder *nv_encoder = NULL, *found = NULL;
struct nouveau_drm *drm = nouveau_drm(dev);
struct nvkm_gpio *gpio = nvxx_gpio(&drm->client.device);
struct nouveau_encoder *nv_encoder = NULL;
struct drm_encoder *encoder; struct drm_encoder *encoder;
int i, panel = -ENODEV; int i, ret;
bool switcheroo_ddc = false;
/* eDP panels need powering on by us (if the VBIOS doesn't default it
* to on) before doing any AUX channel transactions. LVDS panel power
* is handled by the SOR itself, and not required for LVDS DDC.
*/
if (nv_connector->type == DCB_CONNECTOR_eDP) {
panel = nvkm_gpio_get(gpio, 0, DCB_GPIO_PANEL_POWER, 0xff);
if (panel == 0) {
nvkm_gpio_set(gpio, 0, DCB_GPIO_PANEL_POWER, 0xff, 1);
msleep(300);
}
}
drm_connector_for_each_possible_encoder(connector, encoder, i) { drm_connector_for_each_possible_encoder(connector, encoder, i) {
nv_encoder = nouveau_encoder(encoder); nv_encoder = nouveau_encoder(encoder);
if (nv_encoder->dcb->type == DCB_OUTPUT_DP) { switch (nv_encoder->dcb->type) {
int ret = nouveau_dp_detect(nv_encoder); case DCB_OUTPUT_DP:
ret = nouveau_dp_detect(nv_encoder);
if (ret == NOUVEAU_DP_MST) if (ret == NOUVEAU_DP_MST)
return NULL; return NULL;
if (ret == NOUVEAU_DP_SST) else if (ret == NOUVEAU_DP_SST)
break; found = nv_encoder;
} else
if ((vga_switcheroo_handler_flags() & break;
VGA_SWITCHEROO_CAN_SWITCH_DDC) && case DCB_OUTPUT_LVDS:
nv_encoder->dcb->type == DCB_OUTPUT_LVDS && switcheroo_ddc = !!(vga_switcheroo_handler_flags() &
nv_encoder->i2c) { VGA_SWITCHEROO_CAN_SWITCH_DDC);
int ret; /* fall-through */
vga_switcheroo_lock_ddc(dev->pdev); default:
ret = nvkm_probe_i2c(nv_encoder->i2c, 0x50); if (!nv_encoder->i2c)
vga_switcheroo_unlock_ddc(dev->pdev);
if (ret)
break; break;
} else
if (nv_encoder->i2c) { if (switcheroo_ddc)
vga_switcheroo_lock_ddc(dev->pdev);
if (nvkm_probe_i2c(nv_encoder->i2c, 0x50)) if (nvkm_probe_i2c(nv_encoder->i2c, 0x50))
break; found = nv_encoder;
if (switcheroo_ddc)
vga_switcheroo_unlock_ddc(dev->pdev);
break;
} }
if (found)
break;
} }
/* eDP panel not detected, restore panel power GPIO to previous return found;
* state to avoid confusing the SOR for other output types.
*/
if (!nv_encoder && panel == 0)
nvkm_gpio_set(gpio, 0, DCB_GPIO_PANEL_POWER, 0xff, panel);
return nv_encoder;
} }
static struct nouveau_encoder * static struct nouveau_encoder *
...@@ -555,12 +541,16 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) ...@@ -555,12 +541,16 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
nv_connector->edid = NULL; nv_connector->edid = NULL;
} }
/* Outputs are only polled while runtime active, so acquiring a /* Outputs are only polled while runtime active, so resuming the
* runtime PM ref here is unnecessary (and would deadlock upon * device here is unnecessary (and would deadlock upon runtime suspend
* runtime suspend because it waits for polling to finish). * because it waits for polling to finish). We do however, want to
* prevent the autosuspend timer from elapsing during this operation
* if possible.
*/ */
if (!drm_kms_helper_is_poll_worker()) { if (drm_kms_helper_is_poll_worker()) {
ret = pm_runtime_get_sync(connector->dev->dev); pm_runtime_get_noresume(dev->dev);
} else {
ret = pm_runtime_get_sync(dev->dev);
if (ret < 0 && ret != -EACCES) if (ret < 0 && ret != -EACCES)
return conn_status; return conn_status;
} }
...@@ -638,10 +628,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) ...@@ -638,10 +628,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
out: out:
if (!drm_kms_helper_is_poll_worker()) { pm_runtime_mark_last_busy(dev->dev);
pm_runtime_mark_last_busy(connector->dev->dev); pm_runtime_put_autosuspend(dev->dev);
pm_runtime_put_autosuspend(connector->dev->dev);
}
return conn_status; return conn_status;
} }
...@@ -1105,6 +1093,26 @@ nouveau_connector_hotplug(struct nvif_notify *notify) ...@@ -1105,6 +1093,26 @@ nouveau_connector_hotplug(struct nvif_notify *notify)
const struct nvif_notify_conn_rep_v0 *rep = notify->data; const struct nvif_notify_conn_rep_v0 *rep = notify->data;
const char *name = connector->name; const char *name = connector->name;
struct nouveau_encoder *nv_encoder; struct nouveau_encoder *nv_encoder;
int ret;
ret = pm_runtime_get(drm->dev->dev);
if (ret == 0) {
/* We can't block here if there's a pending PM request
* running, as we'll deadlock nouveau_display_fini() when it
* calls nvif_put() on our nvif_notify struct. So, simply
* defer the hotplug event until the device finishes resuming
*/
NV_DEBUG(drm, "Deferring HPD on %s until runtime resume\n",
name);
schedule_work(&drm->hpd_work);
pm_runtime_put_noidle(drm->dev->dev);
return NVIF_NOTIFY_KEEP;
} else if (ret != 1 && ret != -EACCES) {
NV_WARN(drm, "HPD on %s dropped due to RPM failure: %d\n",
name, ret);
return NVIF_NOTIFY_DROP;
}
if (rep->mask & NVIF_NOTIFY_CONN_V0_IRQ) { if (rep->mask & NVIF_NOTIFY_CONN_V0_IRQ) {
NV_DEBUG(drm, "service %s\n", name); NV_DEBUG(drm, "service %s\n", name);
...@@ -1122,6 +1130,8 @@ nouveau_connector_hotplug(struct nvif_notify *notify) ...@@ -1122,6 +1130,8 @@ nouveau_connector_hotplug(struct nvif_notify *notify)
drm_helper_hpd_irq_event(connector->dev); drm_helper_hpd_irq_event(connector->dev);
} }
pm_runtime_mark_last_busy(drm->dev->dev);
pm_runtime_put_autosuspend(drm->dev->dev);
return NVIF_NOTIFY_KEEP; return NVIF_NOTIFY_KEEP;
} }
......
...@@ -293,7 +293,7 @@ nouveau_user_framebuffer_create(struct drm_device *dev, ...@@ -293,7 +293,7 @@ nouveau_user_framebuffer_create(struct drm_device *dev,
static const struct drm_mode_config_funcs nouveau_mode_config_funcs = { static const struct drm_mode_config_funcs nouveau_mode_config_funcs = {
.fb_create = nouveau_user_framebuffer_create, .fb_create = nouveau_user_framebuffer_create,
.output_poll_changed = drm_fb_helper_output_poll_changed, .output_poll_changed = nouveau_fbcon_output_poll_changed,
}; };
...@@ -355,8 +355,6 @@ nouveau_display_hpd_work(struct work_struct *work) ...@@ -355,8 +355,6 @@ nouveau_display_hpd_work(struct work_struct *work)
pm_runtime_get_sync(drm->dev->dev); pm_runtime_get_sync(drm->dev->dev);
drm_helper_hpd_irq_event(drm->dev); drm_helper_hpd_irq_event(drm->dev);
/* enable polling for external displays */
drm_kms_helper_poll_enable(drm->dev);
pm_runtime_mark_last_busy(drm->dev->dev); pm_runtime_mark_last_busy(drm->dev->dev);
pm_runtime_put_sync(drm->dev->dev); pm_runtime_put_sync(drm->dev->dev);
...@@ -379,15 +377,29 @@ nouveau_display_acpi_ntfy(struct notifier_block *nb, unsigned long val, ...@@ -379,15 +377,29 @@ nouveau_display_acpi_ntfy(struct notifier_block *nb, unsigned long val,
{ {
struct nouveau_drm *drm = container_of(nb, typeof(*drm), acpi_nb); struct nouveau_drm *drm = container_of(nb, typeof(*drm), acpi_nb);
struct acpi_bus_event *info = data; struct acpi_bus_event *info = data;
int ret;
if (!strcmp(info->device_class, ACPI_VIDEO_CLASS)) { if (!strcmp(info->device_class, ACPI_VIDEO_CLASS)) {
if (info->type == ACPI_VIDEO_NOTIFY_PROBE) { if (info->type == ACPI_VIDEO_NOTIFY_PROBE) {
/* ret = pm_runtime_get(drm->dev->dev);
* This may be the only indication we receive of a if (ret == 1 || ret == -EACCES) {
* connector hotplug on a runtime suspended GPU, /* If the GPU is already awake, or in a state
* schedule hpd_work to check. * where we can't wake it up, it can handle
*/ * it's own hotplug events.
schedule_work(&drm->hpd_work); */
pm_runtime_put_autosuspend(drm->dev->dev);
} else if (ret == 0) {
/* This may be the only indication we receive
* of a connector hotplug on a runtime
* suspended GPU, schedule hpd_work to check.
*/
NV_DEBUG(drm, "ACPI requested connector reprobe\n");
schedule_work(&drm->hpd_work);
pm_runtime_put_noidle(drm->dev->dev);
} else {
NV_WARN(drm, "Dropped ACPI reprobe event due to RPM error: %d\n",
ret);
}
/* acpi-video should not generate keypresses for this */ /* acpi-video should not generate keypresses for this */
return NOTIFY_BAD; return NOTIFY_BAD;
...@@ -411,6 +423,11 @@ nouveau_display_init(struct drm_device *dev) ...@@ -411,6 +423,11 @@ nouveau_display_init(struct drm_device *dev)
if (ret) if (ret)
return ret; return ret;
/* enable connector detection and polling for connectors without HPD
* support
*/
drm_kms_helper_poll_enable(dev);
/* enable hotplug interrupts */ /* enable hotplug interrupts */
drm_connector_list_iter_begin(dev, &conn_iter); drm_connector_list_iter_begin(dev, &conn_iter);
nouveau_for_each_non_mst_connector_iter(connector, &conn_iter) { nouveau_for_each_non_mst_connector_iter(connector, &conn_iter) {
...@@ -425,7 +442,7 @@ nouveau_display_init(struct drm_device *dev) ...@@ -425,7 +442,7 @@ nouveau_display_init(struct drm_device *dev)
} }
void void
nouveau_display_fini(struct drm_device *dev, bool suspend) nouveau_display_fini(struct drm_device *dev, bool suspend, bool runtime)
{ {
struct nouveau_display *disp = nouveau_display(dev); struct nouveau_display *disp = nouveau_display(dev);
struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_drm *drm = nouveau_drm(dev);
...@@ -450,6 +467,9 @@ nouveau_display_fini(struct drm_device *dev, bool suspend) ...@@ -450,6 +467,9 @@ nouveau_display_fini(struct drm_device *dev, bool suspend)
} }
drm_connector_list_iter_end(&conn_iter); drm_connector_list_iter_end(&conn_iter);
if (!runtime)
cancel_work_sync(&drm->hpd_work);
drm_kms_helper_poll_disable(dev); drm_kms_helper_poll_disable(dev);
disp->fini(dev); disp->fini(dev);
} }
...@@ -618,11 +638,11 @@ nouveau_display_suspend(struct drm_device *dev, bool runtime) ...@@ -618,11 +638,11 @@ nouveau_display_suspend(struct drm_device *dev, bool runtime)
} }
} }
nouveau_display_fini(dev, true); nouveau_display_fini(dev, true, runtime);
return 0; return 0;
} }
nouveau_display_fini(dev, true); nouveau_display_fini(dev, true, runtime);
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
struct nouveau_framebuffer *nouveau_fb; struct nouveau_framebuffer *nouveau_fb;
......
...@@ -62,7 +62,7 @@ nouveau_display(struct drm_device *dev) ...@@ -62,7 +62,7 @@ nouveau_display(struct drm_device *dev)
int nouveau_display_create(struct drm_device *dev); int nouveau_display_create(struct drm_device *dev);
void nouveau_display_destroy(struct drm_device *dev); void nouveau_display_destroy(struct drm_device *dev);
int nouveau_display_init(struct drm_device *dev); int nouveau_display_init(struct drm_device *dev);
void nouveau_display_fini(struct drm_device *dev, bool suspend); void nouveau_display_fini(struct drm_device *dev, bool suspend, bool runtime);
int nouveau_display_suspend(struct drm_device *dev, bool runtime); int nouveau_display_suspend(struct drm_device *dev, bool runtime);
void nouveau_display_resume(struct drm_device *dev, bool runtime); void nouveau_display_resume(struct drm_device *dev, bool runtime);
int nouveau_display_vblank_enable(struct drm_device *, unsigned int); int nouveau_display_vblank_enable(struct drm_device *, unsigned int);
......
...@@ -230,7 +230,7 @@ nouveau_cli_init(struct nouveau_drm *drm, const char *sname, ...@@ -230,7 +230,7 @@ nouveau_cli_init(struct nouveau_drm *drm, const char *sname,
mutex_unlock(&drm->master.lock); mutex_unlock(&drm->master.lock);
} }
if (ret) { if (ret) {
NV_ERROR(drm, "Client allocation failed: %d\n", ret); NV_PRINTK(err, cli, "Client allocation failed: %d\n", ret);
goto done; goto done;
} }
...@@ -240,37 +240,37 @@ nouveau_cli_init(struct nouveau_drm *drm, const char *sname, ...@@ -240,37 +240,37 @@ nouveau_cli_init(struct nouveau_drm *drm, const char *sname,
}, sizeof(struct nv_device_v0), }, sizeof(struct nv_device_v0),
&cli->device); &cli->device);
if (ret) { if (ret) {
NV_ERROR(drm, "Device allocation failed: %d\n", ret); NV_PRINTK(err, cli, "Device allocation failed: %d\n", ret);
goto done; goto done;
} }
ret = nvif_mclass(&cli->device.object, mmus); ret = nvif_mclass(&cli->device.object, mmus);
if (ret < 0) { if (ret < 0) {
NV_ERROR(drm, "No supported MMU class\n"); NV_PRINTK(err, cli, "No supported MMU class\n");
goto done; goto done;
} }
ret = nvif_mmu_init(&cli->device.object, mmus[ret].oclass, &cli->mmu); ret = nvif_mmu_init(&cli->device.object, mmus[ret].oclass, &cli->mmu);
if (ret) { if (ret) {
NV_ERROR(drm, "MMU allocation failed: %d\n", ret); NV_PRINTK(err, cli, "MMU allocation failed: %d\n", ret);
goto done; goto done;
} }
ret = nvif_mclass(&cli->mmu.object, vmms); ret = nvif_mclass(&cli->mmu.object, vmms);
if (ret < 0) { if (ret < 0) {
NV_ERROR(drm, "No supported VMM class\n"); NV_PRINTK(err, cli, "No supported VMM class\n");
goto done; goto done;
} }
ret = nouveau_vmm_init(cli, vmms[ret].oclass, &cli->vmm); ret = nouveau_vmm_init(cli, vmms[ret].oclass, &cli->vmm);
if (ret) { if (ret) {
NV_ERROR(drm, "VMM allocation failed: %d\n", ret); NV_PRINTK(err, cli, "VMM allocation failed: %d\n", ret);
goto done; goto done;
} }
ret = nvif_mclass(&cli->mmu.object, mems); ret = nvif_mclass(&cli->mmu.object, mems);
if (ret < 0) { if (ret < 0) {
NV_ERROR(drm, "No supported MEM class\n"); NV_PRINTK(err, cli, "No supported MEM class\n");
goto done; goto done;
} }
...@@ -592,10 +592,8 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags) ...@@ -592,10 +592,8 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags)
pm_runtime_allow(dev->dev); pm_runtime_allow(dev->dev);
pm_runtime_mark_last_busy(dev->dev); pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put(dev->dev); pm_runtime_put(dev->dev);
} else {
/* enable polling for external displays */
drm_kms_helper_poll_enable(dev);
} }
return 0; return 0;
fail_dispinit: fail_dispinit:
...@@ -629,7 +627,7 @@ nouveau_drm_unload(struct drm_device *dev) ...@@ -629,7 +627,7 @@ nouveau_drm_unload(struct drm_device *dev)
nouveau_debugfs_fini(drm); nouveau_debugfs_fini(drm);
if (dev->mode_config.num_crtc) if (dev->mode_config.num_crtc)
nouveau_display_fini(dev, false); nouveau_display_fini(dev, false, false);
nouveau_display_destroy(dev); nouveau_display_destroy(dev);
nouveau_bios_takedown(dev); nouveau_bios_takedown(dev);
...@@ -835,7 +833,6 @@ nouveau_pmops_runtime_suspend(struct device *dev) ...@@ -835,7 +833,6 @@ nouveau_pmops_runtime_suspend(struct device *dev)
return -EBUSY; return -EBUSY;
} }
drm_kms_helper_poll_disable(drm_dev);
nouveau_switcheroo_optimus_dsm(); nouveau_switcheroo_optimus_dsm();
ret = nouveau_do_suspend(drm_dev, true); ret = nouveau_do_suspend(drm_dev, true);
pci_save_state(pdev); pci_save_state(pdev);
......
...@@ -466,6 +466,7 @@ nouveau_fbcon_set_suspend_work(struct work_struct *work) ...@@ -466,6 +466,7 @@ nouveau_fbcon_set_suspend_work(struct work_struct *work)
console_unlock(); console_unlock();
if (state == FBINFO_STATE_RUNNING) { if (state == FBINFO_STATE_RUNNING) {
nouveau_fbcon_hotplug_resume(drm->fbcon);
pm_runtime_mark_last_busy(drm->dev->dev); pm_runtime_mark_last_busy(drm->dev->dev);
pm_runtime_put_sync(drm->dev->dev); pm_runtime_put_sync(drm->dev->dev);
} }
...@@ -487,6 +488,61 @@ nouveau_fbcon_set_suspend(struct drm_device *dev, int state) ...@@ -487,6 +488,61 @@ nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
schedule_work(&drm->fbcon_work); schedule_work(&drm->fbcon_work);
} }
void
nouveau_fbcon_output_poll_changed(struct drm_device *dev)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_fbdev *fbcon = drm->fbcon;
int ret;
if (!fbcon)
return;
mutex_lock(&fbcon->hotplug_lock);
ret = pm_runtime_get(dev->dev);
if (ret == 1 || ret == -EACCES) {
drm_fb_helper_hotplug_event(&fbcon->helper);
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
} else if (ret == 0) {
/* If the GPU was already in the process of suspending before
* this event happened, then we can't block here as we'll
* deadlock the runtime pmops since they wait for us to
* finish. So, just defer this event for when we runtime
* resume again. It will be handled by fbcon_work.
*/
NV_DEBUG(drm, "fbcon HPD event deferred until runtime resume\n");
fbcon->hotplug_waiting = true;
pm_runtime_put_noidle(drm->dev->dev);
} else {
DRM_WARN("fbcon HPD event lost due to RPM failure: %d\n",
ret);
}
mutex_unlock(&fbcon->hotplug_lock);
}
void
nouveau_fbcon_hotplug_resume(struct nouveau_fbdev *fbcon)
{
struct nouveau_drm *drm;
if (!fbcon)
return;
drm = nouveau_drm(fbcon->helper.dev);
mutex_lock(&fbcon->hotplug_lock);
if (fbcon->hotplug_waiting) {
fbcon->hotplug_waiting = false;
NV_DEBUG(drm, "Handling deferred fbcon HPD events\n");
drm_fb_helper_hotplug_event(&fbcon->helper);
}
mutex_unlock(&fbcon->hotplug_lock);
}
int int
nouveau_fbcon_init(struct drm_device *dev) nouveau_fbcon_init(struct drm_device *dev)
{ {
...@@ -505,6 +561,7 @@ nouveau_fbcon_init(struct drm_device *dev) ...@@ -505,6 +561,7 @@ nouveau_fbcon_init(struct drm_device *dev)
drm->fbcon = fbcon; drm->fbcon = fbcon;
INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work); INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work);
mutex_init(&fbcon->hotplug_lock);
drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs); drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs);
......
...@@ -41,6 +41,9 @@ struct nouveau_fbdev { ...@@ -41,6 +41,9 @@ struct nouveau_fbdev {
struct nvif_object gdi; struct nvif_object gdi;
struct nvif_object blit; struct nvif_object blit;
struct nvif_object twod; struct nvif_object twod;
struct mutex hotplug_lock;
bool hotplug_waiting;
}; };
void nouveau_fbcon_restore(void); void nouveau_fbcon_restore(void);
...@@ -68,6 +71,8 @@ void nouveau_fbcon_set_suspend(struct drm_device *dev, int state); ...@@ -68,6 +71,8 @@ void nouveau_fbcon_set_suspend(struct drm_device *dev, int state);
void nouveau_fbcon_accel_save_disable(struct drm_device *dev); void nouveau_fbcon_accel_save_disable(struct drm_device *dev);
void nouveau_fbcon_accel_restore(struct drm_device *dev); void nouveau_fbcon_accel_restore(struct drm_device *dev);
void nouveau_fbcon_output_poll_changed(struct drm_device *dev);
void nouveau_fbcon_hotplug_resume(struct nouveau_fbdev *fbcon);
extern int nouveau_nofbaccel; extern int nouveau_nofbaccel;
#endif /* __NV50_FBCON_H__ */ #endif /* __NV50_FBCON_H__ */
......
...@@ -46,12 +46,10 @@ nouveau_switcheroo_set_state(struct pci_dev *pdev, ...@@ -46,12 +46,10 @@ nouveau_switcheroo_set_state(struct pci_dev *pdev,
pr_err("VGA switcheroo: switched nouveau on\n"); pr_err("VGA switcheroo: switched nouveau on\n");
dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; dev->switch_power_state = DRM_SWITCH_POWER_CHANGING;
nouveau_pmops_resume(&pdev->dev); nouveau_pmops_resume(&pdev->dev);
drm_kms_helper_poll_enable(dev);
dev->switch_power_state = DRM_SWITCH_POWER_ON; dev->switch_power_state = DRM_SWITCH_POWER_ON;
} else { } else {
pr_err("VGA switcheroo: switched nouveau off\n"); pr_err("VGA switcheroo: switched nouveau off\n");
dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; dev->switch_power_state = DRM_SWITCH_POWER_CHANGING;
drm_kms_helper_poll_disable(dev);
nouveau_switcheroo_optimus_dsm(); nouveau_switcheroo_optimus_dsm();
nouveau_pmops_suspend(&pdev->dev); nouveau_pmops_suspend(&pdev->dev);
dev->switch_power_state = DRM_SWITCH_POWER_OFF; dev->switch_power_state = DRM_SWITCH_POWER_OFF;
......
...@@ -275,6 +275,7 @@ nvkm_disp_oneinit(struct nvkm_engine *engine) ...@@ -275,6 +275,7 @@ nvkm_disp_oneinit(struct nvkm_engine *engine)
struct nvkm_outp *outp, *outt, *pair; struct nvkm_outp *outp, *outt, *pair;
struct nvkm_conn *conn; struct nvkm_conn *conn;
struct nvkm_head *head; struct nvkm_head *head;
struct nvkm_ior *ior;
struct nvbios_connE connE; struct nvbios_connE connE;
struct dcb_output dcbE; struct dcb_output dcbE;
u8 hpd = 0, ver, hdr; u8 hpd = 0, ver, hdr;
...@@ -399,6 +400,19 @@ nvkm_disp_oneinit(struct nvkm_engine *engine) ...@@ -399,6 +400,19 @@ nvkm_disp_oneinit(struct nvkm_engine *engine)
return ret; return ret;
} }
/* Enforce identity-mapped SOR assignment for panels, which have
* certain bits (ie. backlight controls) wired to a specific SOR.
*/
list_for_each_entry(outp, &disp->outp, head) {
if (outp->conn->info.type == DCB_CONNECTOR_LVDS ||
outp->conn->info.type == DCB_CONNECTOR_eDP) {
ior = nvkm_ior_find(disp, SOR, ffs(outp->info.or) - 1);
if (!WARN_ON(!ior))
ior->identity = true;
outp->identity = true;
}
}
i = 0; i = 0;
list_for_each_entry(head, &disp->head, head) list_for_each_entry(head, &disp->head, head)
i = max(i, head->id + 1); i = max(i, head->id + 1);
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include <subdev/bios.h> #include <subdev/bios.h>
#include <subdev/bios/init.h> #include <subdev/bios/init.h>
#include <subdev/gpio.h>
#include <subdev/i2c.h> #include <subdev/i2c.h>
#include <nvif/event.h> #include <nvif/event.h>
...@@ -412,14 +413,10 @@ nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps) ...@@ -412,14 +413,10 @@ nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps)
} }
static void static void
nvkm_dp_release(struct nvkm_outp *outp, struct nvkm_ior *ior) nvkm_dp_disable(struct nvkm_outp *outp, struct nvkm_ior *ior)
{ {
struct nvkm_dp *dp = nvkm_dp(outp); struct nvkm_dp *dp = nvkm_dp(outp);
/* Prevent link from being retrained if sink sends an IRQ. */
atomic_set(&dp->lt.done, 0);
ior->dp.nr = 0;
/* Execute DisableLT script from DP Info Table. */ /* Execute DisableLT script from DP Info Table. */
nvbios_init(&ior->disp->engine.subdev, dp->info.script[4], nvbios_init(&ior->disp->engine.subdev, dp->info.script[4],
init.outp = &dp->outp.info; init.outp = &dp->outp.info;
...@@ -428,6 +425,16 @@ nvkm_dp_release(struct nvkm_outp *outp, struct nvkm_ior *ior) ...@@ -428,6 +425,16 @@ nvkm_dp_release(struct nvkm_outp *outp, struct nvkm_ior *ior)
); );
} }
static void
nvkm_dp_release(struct nvkm_outp *outp)
{
struct nvkm_dp *dp = nvkm_dp(outp);
/* Prevent link from being retrained if sink sends an IRQ. */
atomic_set(&dp->lt.done, 0);
dp->outp.ior->dp.nr = 0;
}
static int static int
nvkm_dp_acquire(struct nvkm_outp *outp) nvkm_dp_acquire(struct nvkm_outp *outp)
{ {
...@@ -491,7 +498,7 @@ nvkm_dp_acquire(struct nvkm_outp *outp) ...@@ -491,7 +498,7 @@ nvkm_dp_acquire(struct nvkm_outp *outp)
return ret; return ret;
} }
static void static bool
nvkm_dp_enable(struct nvkm_dp *dp, bool enable) nvkm_dp_enable(struct nvkm_dp *dp, bool enable)
{ {
struct nvkm_i2c_aux *aux = dp->aux; struct nvkm_i2c_aux *aux = dp->aux;
...@@ -505,7 +512,7 @@ nvkm_dp_enable(struct nvkm_dp *dp, bool enable) ...@@ -505,7 +512,7 @@ nvkm_dp_enable(struct nvkm_dp *dp, bool enable)
if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, dp->dpcd, if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, dp->dpcd,
sizeof(dp->dpcd))) sizeof(dp->dpcd)))
return; return true;
} }
if (dp->present) { if (dp->present) {
...@@ -515,6 +522,7 @@ nvkm_dp_enable(struct nvkm_dp *dp, bool enable) ...@@ -515,6 +522,7 @@ nvkm_dp_enable(struct nvkm_dp *dp, bool enable)
} }
atomic_set(&dp->lt.done, 0); atomic_set(&dp->lt.done, 0);
return false;
} }
static int static int
...@@ -555,9 +563,38 @@ nvkm_dp_fini(struct nvkm_outp *outp) ...@@ -555,9 +563,38 @@ nvkm_dp_fini(struct nvkm_outp *outp)
static void static void
nvkm_dp_init(struct nvkm_outp *outp) nvkm_dp_init(struct nvkm_outp *outp)
{ {
struct nvkm_gpio *gpio = outp->disp->engine.subdev.device->gpio;
struct nvkm_dp *dp = nvkm_dp(outp); struct nvkm_dp *dp = nvkm_dp(outp);
nvkm_notify_put(&dp->outp.conn->hpd); nvkm_notify_put(&dp->outp.conn->hpd);
nvkm_dp_enable(dp, true);
/* eDP panels need powering on by us (if the VBIOS doesn't default it
* to on) before doing any AUX channel transactions. LVDS panel power
* is handled by the SOR itself, and not required for LVDS DDC.
*/
if (dp->outp.conn->info.type == DCB_CONNECTOR_eDP) {
int power = nvkm_gpio_get(gpio, 0, DCB_GPIO_PANEL_POWER, 0xff);
if (power == 0)
nvkm_gpio_set(gpio, 0, DCB_GPIO_PANEL_POWER, 0xff, 1);
/* We delay here unconditionally, even if already powered,
* because some laptop panels having a significant resume
* delay before the panel begins responding.
*
* This is likely a bit of a hack, but no better idea for
* handling this at the moment.
*/
msleep(300);
/* If the eDP panel can't be detected, we need to restore
* the panel power GPIO to avoid breaking another output.
*/
if (!nvkm_dp_enable(dp, true) && power == 0)
nvkm_gpio_set(gpio, 0, DCB_GPIO_PANEL_POWER, 0xff, 0);
} else {
nvkm_dp_enable(dp, true);
}
nvkm_notify_get(&dp->hpd); nvkm_notify_get(&dp->hpd);
} }
...@@ -576,6 +613,7 @@ nvkm_dp_func = { ...@@ -576,6 +613,7 @@ nvkm_dp_func = {
.fini = nvkm_dp_fini, .fini = nvkm_dp_fini,
.acquire = nvkm_dp_acquire, .acquire = nvkm_dp_acquire,
.release = nvkm_dp_release, .release = nvkm_dp_release,
.disable = nvkm_dp_disable,
}; };
static int static int
......
...@@ -16,6 +16,7 @@ struct nvkm_ior { ...@@ -16,6 +16,7 @@ struct nvkm_ior {
char name[8]; char name[8];
struct list_head head; struct list_head head;
bool identity;
struct nvkm_ior_state { struct nvkm_ior_state {
struct nvkm_outp *outp; struct nvkm_outp *outp;
......
...@@ -501,11 +501,11 @@ nv50_disp_super_2_0(struct nv50_disp *disp, struct nvkm_head *head) ...@@ -501,11 +501,11 @@ nv50_disp_super_2_0(struct nv50_disp *disp, struct nvkm_head *head)
nv50_disp_super_ied_off(head, ior, 2); nv50_disp_super_ied_off(head, ior, 2);
/* If we're shutting down the OR's only active head, execute /* If we're shutting down the OR's only active head, execute
* the output path's release function. * the output path's disable function.
*/ */
if (ior->arm.head == (1 << head->id)) { if (ior->arm.head == (1 << head->id)) {
if ((outp = ior->arm.outp) && outp->func->release) if ((outp = ior->arm.outp) && outp->func->disable)
outp->func->release(outp, ior); outp->func->disable(outp, ior);
} }
} }
......
...@@ -93,6 +93,8 @@ nvkm_outp_release(struct nvkm_outp *outp, u8 user) ...@@ -93,6 +93,8 @@ nvkm_outp_release(struct nvkm_outp *outp, u8 user)
if (ior) { if (ior) {
outp->acquired &= ~user; outp->acquired &= ~user;
if (!outp->acquired) { if (!outp->acquired) {
if (outp->func->release && outp->ior)
outp->func->release(outp);
outp->ior->asy.outp = NULL; outp->ior->asy.outp = NULL;
outp->ior = NULL; outp->ior = NULL;
} }
...@@ -127,17 +129,26 @@ nvkm_outp_acquire(struct nvkm_outp *outp, u8 user) ...@@ -127,17 +129,26 @@ nvkm_outp_acquire(struct nvkm_outp *outp, u8 user)
if (proto == UNKNOWN) if (proto == UNKNOWN)
return -ENOSYS; return -ENOSYS;
/* Deal with panels requiring identity-mapped SOR assignment. */
if (outp->identity) {
ior = nvkm_ior_find(outp->disp, SOR, ffs(outp->info.or) - 1);
if (WARN_ON(!ior))
return -ENOSPC;
return nvkm_outp_acquire_ior(outp, user, ior);
}
/* First preference is to reuse the OR that is currently armed /* First preference is to reuse the OR that is currently armed
* on HW, if any, in order to prevent unnecessary switching. * on HW, if any, in order to prevent unnecessary switching.
*/ */
list_for_each_entry(ior, &outp->disp->ior, head) { list_for_each_entry(ior, &outp->disp->ior, head) {
if (!ior->asy.outp && ior->arm.outp == outp) if (!ior->identity && !ior->asy.outp && ior->arm.outp == outp)
return nvkm_outp_acquire_ior(outp, user, ior); return nvkm_outp_acquire_ior(outp, user, ior);
} }
/* Failing that, a completely unused OR is the next best thing. */ /* Failing that, a completely unused OR is the next best thing. */
list_for_each_entry(ior, &outp->disp->ior, head) { list_for_each_entry(ior, &outp->disp->ior, head) {
if (!ior->asy.outp && ior->type == type && !ior->arm.outp && if (!ior->identity &&
!ior->asy.outp && ior->type == type && !ior->arm.outp &&
(ior->func->route.set || ior->id == __ffs(outp->info.or))) (ior->func->route.set || ior->id == __ffs(outp->info.or)))
return nvkm_outp_acquire_ior(outp, user, ior); return nvkm_outp_acquire_ior(outp, user, ior);
} }
...@@ -146,7 +157,7 @@ nvkm_outp_acquire(struct nvkm_outp *outp, u8 user) ...@@ -146,7 +157,7 @@ nvkm_outp_acquire(struct nvkm_outp *outp, u8 user)
* but will be released during the next modeset. * but will be released during the next modeset.
*/ */
list_for_each_entry(ior, &outp->disp->ior, head) { list_for_each_entry(ior, &outp->disp->ior, head) {
if (!ior->asy.outp && ior->type == type && if (!ior->identity && !ior->asy.outp && ior->type == type &&
(ior->func->route.set || ior->id == __ffs(outp->info.or))) (ior->func->route.set || ior->id == __ffs(outp->info.or)))
return nvkm_outp_acquire_ior(outp, user, ior); return nvkm_outp_acquire_ior(outp, user, ior);
} }
...@@ -245,7 +256,6 @@ nvkm_outp_ctor(const struct nvkm_outp_func *func, struct nvkm_disp *disp, ...@@ -245,7 +256,6 @@ nvkm_outp_ctor(const struct nvkm_outp_func *func, struct nvkm_disp *disp,
outp->index = index; outp->index = index;
outp->info = *dcbE; outp->info = *dcbE;
outp->i2c = nvkm_i2c_bus_find(i2c, dcbE->i2c_index); outp->i2c = nvkm_i2c_bus_find(i2c, dcbE->i2c_index);
outp->or = ffs(outp->info.or) - 1;
OUTP_DBG(outp, "type %02x loc %d or %d link %d con %x " OUTP_DBG(outp, "type %02x loc %d or %d link %d con %x "
"edid %x bus %d head %x", "edid %x bus %d head %x",
......
...@@ -13,10 +13,10 @@ struct nvkm_outp { ...@@ -13,10 +13,10 @@ struct nvkm_outp {
struct dcb_output info; struct dcb_output info;
struct nvkm_i2c_bus *i2c; struct nvkm_i2c_bus *i2c;
int or;
struct list_head head; struct list_head head;
struct nvkm_conn *conn; struct nvkm_conn *conn;
bool identity;
/* Assembly state. */ /* Assembly state. */
#define NVKM_OUTP_PRIV 1 #define NVKM_OUTP_PRIV 1
...@@ -41,7 +41,8 @@ struct nvkm_outp_func { ...@@ -41,7 +41,8 @@ struct nvkm_outp_func {
void (*init)(struct nvkm_outp *); void (*init)(struct nvkm_outp *);
void (*fini)(struct nvkm_outp *); void (*fini)(struct nvkm_outp *);
int (*acquire)(struct nvkm_outp *); int (*acquire)(struct nvkm_outp *);
void (*release)(struct nvkm_outp *, struct nvkm_ior *); void (*release)(struct nvkm_outp *);
void (*disable)(struct nvkm_outp *, struct nvkm_ior *);
}; };
#define OUTP_MSG(o,l,f,a...) do { \ #define OUTP_MSG(o,l,f,a...) do { \
......
...@@ -158,7 +158,8 @@ gm200_devinit_post(struct nvkm_devinit *base, bool post) ...@@ -158,7 +158,8 @@ gm200_devinit_post(struct nvkm_devinit *base, bool post)
} }
/* load and execute some other ucode image (bios therm?) */ /* load and execute some other ucode image (bios therm?) */
return pmu_load(init, 0x01, post, NULL, NULL); pmu_load(init, 0x01, post, NULL, NULL);
return 0;
} }
static const struct nvkm_devinit_func static const struct nvkm_devinit_func
......
...@@ -1423,7 +1423,7 @@ nvkm_vmm_get(struct nvkm_vmm *vmm, u8 page, u64 size, struct nvkm_vma **pvma) ...@@ -1423,7 +1423,7 @@ nvkm_vmm_get(struct nvkm_vmm *vmm, u8 page, u64 size, struct nvkm_vma **pvma)
void void
nvkm_vmm_part(struct nvkm_vmm *vmm, struct nvkm_memory *inst) nvkm_vmm_part(struct nvkm_vmm *vmm, struct nvkm_memory *inst)
{ {
if (vmm->func->part && inst) { if (inst && vmm->func->part) {
mutex_lock(&vmm->mutex); mutex_lock(&vmm->mutex);
vmm->func->part(vmm, inst); vmm->func->part(vmm, inst);
mutex_unlock(&vmm->mutex); mutex_unlock(&vmm->mutex);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment