mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 06:31:49 +00:00
nouveau runpm and MST race fixes.
-----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJbmLg2AAoJEAx081l5xIa+794QAJeBd7XnrQtMS+BWMet0G4Dh yY1t3LAsP75GiyI73Zbf1mWIdNk1hxli9+9+XVSrK9Yt2oh/AD+Q8BBGW7V3b17S BvpAhKdQNJut8NskfTKvFE5lz/oMrJCKYFYh/vwigfOXo9ELg9imUcKMQJqHLzPP SEiS4EgQ00h97FSodqYLj8E8sSIGx61xKkCBgczcpXq6SrxQAIZGW0Ut7cKUebl+ VON1sccI6xKCqVHqv/mqkm/Cugc5c6bj+AUII9NvXljAKptNjb8c813XbjwCCfzC 5OMX084ZXl94jAbqZjJXDYu640tkUpx1+o3eJZOfWz4dM7oTWU59+AHxytQWmF8P T73o9+r93cQSP0YUb070LkSPr/YSBldb3PFk22mzRX1kHeHPuLzMGGr37Xj/3RI9 Uilddus6n2S7JzLdPl3RNLnDfVwRopc/E7VUbUCMDKrbHjV2tvsyZvBDDgPXCU68 cxG/VXKQsa4yicMbXUCjl+vGwaXZLve1wGGx/ee9Ooaqa7b7L297NWpxqhl3PjhL G2gySbwa7gItRPw34fhNlzmEoNFk+JyAp3EOYOZQM78+IBKRSwhDVJOrbAjY+H1F c+sUOdBk+yLQ15MVbhFeq+1puJETnV3ZmJgHjNDUUhRcJamilqfLBNiNUfGm4Pqp 9E8QZTq8BgZqWJE9ep0m =GbIW -----END PGP SIGNATURE----- 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
This commit is contained in:
commit
7428b2e5d0
@ -1123,17 +1123,21 @@ nv50_mstm_enable(struct nv50_mstm *mstm, u8 dpcd, int state)
|
||||
int ret;
|
||||
|
||||
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)
|
||||
return ret;
|
||||
|
||||
dpcd &= ~DP_MST_EN;
|
||||
if (state)
|
||||
dpcd |= DP_MST_EN;
|
||||
|
||||
ret = drm_dp_dpcd_writeb(mstm->mgr.aux, DP_MSTM_CTRL, dpcd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (state) {
|
||||
/* Now, start initializing */
|
||||
ret = drm_dp_dpcd_writeb(mstm->mgr.aux, DP_MSTM_CTRL,
|
||||
DP_MST_EN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return nvif_mthd(disp, 0, &args, sizeof(args));
|
||||
@ -1142,31 +1146,58 @@ nv50_mstm_enable(struct nv50_mstm *mstm, u8 dpcd, int state)
|
||||
int
|
||||
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)
|
||||
return 0;
|
||||
|
||||
if (dpcd[0] >= 0x12) {
|
||||
ret = drm_dp_dpcd_readb(mstm->mgr.aux, DP_MSTM_CAP, &dpcd[1]);
|
||||
mutex_lock(&mstm->mgr.lock);
|
||||
|
||||
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)
|
||||
return ret;
|
||||
goto probe_error;
|
||||
|
||||
if (!(dpcd[1] & DP_MST_CAP))
|
||||
dpcd[0] = 0x11;
|
||||
else
|
||||
state = allow;
|
||||
new_state = allow;
|
||||
}
|
||||
|
||||
ret = nv50_mstm_enable(mstm, dpcd[0], state);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (new_state == old_state) {
|
||||
mutex_unlock(&mstm->mgr.lock);
|
||||
return new_state;
|
||||
}
|
||||
|
||||
ret = drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, state);
|
||||
ret = nv50_mstm_enable(mstm, dpcd[0], new_state);
|
||||
if (ret)
|
||||
goto probe_error;
|
||||
|
||||
mutex_unlock(&mstm->mgr.lock);
|
||||
|
||||
ret = drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, new_state);
|
||||
if (ret)
|
||||
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
|
||||
@ -2074,7 +2105,7 @@ nv50_disp_atomic_state_alloc(struct drm_device *dev)
|
||||
static const struct drm_mode_config_funcs
|
||||
nv50_disp_func = {
|
||||
.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_commit = nv50_disp_atomic_commit,
|
||||
.atomic_state_alloc = nv50_disp_atomic_state_alloc,
|
||||
|
@ -409,59 +409,45 @@ static struct nouveau_encoder *
|
||||
nouveau_connector_ddc_detect(struct drm_connector *connector)
|
||||
{
|
||||
struct drm_device *dev = connector->dev;
|
||||
struct nouveau_connector *nv_connector = nouveau_connector(connector);
|
||||
struct nouveau_drm *drm = nouveau_drm(dev);
|
||||
struct nvkm_gpio *gpio = nvxx_gpio(&drm->client.device);
|
||||
struct nouveau_encoder *nv_encoder = NULL;
|
||||
struct nouveau_encoder *nv_encoder = NULL, *found = NULL;
|
||||
struct drm_encoder *encoder;
|
||||
int i, panel = -ENODEV;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
int i, ret;
|
||||
bool switcheroo_ddc = false;
|
||||
|
||||
drm_connector_for_each_possible_encoder(connector, encoder, i) {
|
||||
nv_encoder = nouveau_encoder(encoder);
|
||||
|
||||
if (nv_encoder->dcb->type == DCB_OUTPUT_DP) {
|
||||
int ret = nouveau_dp_detect(nv_encoder);
|
||||
switch (nv_encoder->dcb->type) {
|
||||
case DCB_OUTPUT_DP:
|
||||
ret = nouveau_dp_detect(nv_encoder);
|
||||
if (ret == NOUVEAU_DP_MST)
|
||||
return NULL;
|
||||
if (ret == NOUVEAU_DP_SST)
|
||||
else if (ret == NOUVEAU_DP_SST)
|
||||
found = nv_encoder;
|
||||
|
||||
break;
|
||||
case DCB_OUTPUT_LVDS:
|
||||
switcheroo_ddc = !!(vga_switcheroo_handler_flags() &
|
||||
VGA_SWITCHEROO_CAN_SWITCH_DDC);
|
||||
/* fall-through */
|
||||
default:
|
||||
if (!nv_encoder->i2c)
|
||||
break;
|
||||
} else
|
||||
if ((vga_switcheroo_handler_flags() &
|
||||
VGA_SWITCHEROO_CAN_SWITCH_DDC) &&
|
||||
nv_encoder->dcb->type == DCB_OUTPUT_LVDS &&
|
||||
nv_encoder->i2c) {
|
||||
int ret;
|
||||
vga_switcheroo_lock_ddc(dev->pdev);
|
||||
ret = nvkm_probe_i2c(nv_encoder->i2c, 0x50);
|
||||
vga_switcheroo_unlock_ddc(dev->pdev);
|
||||
if (ret)
|
||||
break;
|
||||
} else
|
||||
if (nv_encoder->i2c) {
|
||||
|
||||
if (switcheroo_ddc)
|
||||
vga_switcheroo_lock_ddc(dev->pdev);
|
||||
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
|
||||
* 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;
|
||||
return found;
|
||||
}
|
||||
|
||||
static struct nouveau_encoder *
|
||||
@ -555,12 +541,16 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
|
||||
nv_connector->edid = NULL;
|
||||
}
|
||||
|
||||
/* Outputs are only polled while runtime active, so acquiring a
|
||||
* runtime PM ref here is unnecessary (and would deadlock upon
|
||||
* runtime suspend because it waits for polling to finish).
|
||||
/* Outputs are only polled while runtime active, so resuming the
|
||||
* device here is unnecessary (and would deadlock upon runtime suspend
|
||||
* 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()) {
|
||||
ret = pm_runtime_get_sync(connector->dev->dev);
|
||||
if (drm_kms_helper_is_poll_worker()) {
|
||||
pm_runtime_get_noresume(dev->dev);
|
||||
} else {
|
||||
ret = pm_runtime_get_sync(dev->dev);
|
||||
if (ret < 0 && ret != -EACCES)
|
||||
return conn_status;
|
||||
}
|
||||
@ -638,10 +628,8 @@ detect_analog:
|
||||
|
||||
out:
|
||||
|
||||
if (!drm_kms_helper_is_poll_worker()) {
|
||||
pm_runtime_mark_last_busy(connector->dev->dev);
|
||||
pm_runtime_put_autosuspend(connector->dev->dev);
|
||||
}
|
||||
pm_runtime_mark_last_busy(dev->dev);
|
||||
pm_runtime_put_autosuspend(dev->dev);
|
||||
|
||||
return conn_status;
|
||||
}
|
||||
@ -1105,6 +1093,26 @@ nouveau_connector_hotplug(struct nvif_notify *notify)
|
||||
const struct nvif_notify_conn_rep_v0 *rep = notify->data;
|
||||
const char *name = connector->name;
|
||||
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) {
|
||||
NV_DEBUG(drm, "service %s\n", name);
|
||||
@ -1122,6 +1130,8 @@ nouveau_connector_hotplug(struct nvif_notify *notify)
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -293,7 +293,7 @@ nouveau_user_framebuffer_create(struct drm_device *dev,
|
||||
|
||||
static const struct drm_mode_config_funcs nouveau_mode_config_funcs = {
|
||||
.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)
|
||||
pm_runtime_get_sync(drm->dev->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_put_sync(drm->dev->dev);
|
||||
@ -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 acpi_bus_event *info = data;
|
||||
int ret;
|
||||
|
||||
if (!strcmp(info->device_class, ACPI_VIDEO_CLASS)) {
|
||||
if (info->type == ACPI_VIDEO_NOTIFY_PROBE) {
|
||||
/*
|
||||
* This may be the only indication we receive of a
|
||||
* connector hotplug on a runtime suspended GPU,
|
||||
* schedule hpd_work to check.
|
||||
*/
|
||||
schedule_work(&drm->hpd_work);
|
||||
ret = pm_runtime_get(drm->dev->dev);
|
||||
if (ret == 1 || ret == -EACCES) {
|
||||
/* If the GPU is already awake, or in a state
|
||||
* where we can't wake it up, it can handle
|
||||
* it's own hotplug events.
|
||||
*/
|
||||
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 */
|
||||
return NOTIFY_BAD;
|
||||
@ -411,6 +423,11 @@ nouveau_display_init(struct drm_device *dev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* enable connector detection and polling for connectors without HPD
|
||||
* support
|
||||
*/
|
||||
drm_kms_helper_poll_enable(dev);
|
||||
|
||||
/* enable hotplug interrupts */
|
||||
drm_connector_list_iter_begin(dev, &conn_iter);
|
||||
nouveau_for_each_non_mst_connector_iter(connector, &conn_iter) {
|
||||
@ -425,7 +442,7 @@ nouveau_display_init(struct drm_device *dev)
|
||||
}
|
||||
|
||||
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_drm *drm = nouveau_drm(dev);
|
||||
@ -450,6 +467,9 @@ nouveau_display_fini(struct drm_device *dev, bool suspend)
|
||||
}
|
||||
drm_connector_list_iter_end(&conn_iter);
|
||||
|
||||
if (!runtime)
|
||||
cancel_work_sync(&drm->hpd_work);
|
||||
|
||||
drm_kms_helper_poll_disable(dev);
|
||||
disp->fini(dev);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
nouveau_display_fini(dev, true);
|
||||
nouveau_display_fini(dev, true, runtime);
|
||||
|
||||
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
|
||||
struct nouveau_framebuffer *nouveau_fb;
|
||||
|
@ -62,7 +62,7 @@ nouveau_display(struct drm_device *dev)
|
||||
int nouveau_display_create(struct drm_device *dev);
|
||||
void nouveau_display_destroy(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);
|
||||
void nouveau_display_resume(struct drm_device *dev, bool runtime);
|
||||
int nouveau_display_vblank_enable(struct drm_device *, unsigned int);
|
||||
|
@ -230,7 +230,7 @@ nouveau_cli_init(struct nouveau_drm *drm, const char *sname,
|
||||
mutex_unlock(&drm->master.lock);
|
||||
}
|
||||
if (ret) {
|
||||
NV_ERROR(drm, "Client allocation failed: %d\n", ret);
|
||||
NV_PRINTK(err, cli, "Client allocation failed: %d\n", ret);
|
||||
goto done;
|
||||
}
|
||||
|
||||
@ -240,37 +240,37 @@ nouveau_cli_init(struct nouveau_drm *drm, const char *sname,
|
||||
}, sizeof(struct nv_device_v0),
|
||||
&cli->device);
|
||||
if (ret) {
|
||||
NV_ERROR(drm, "Device allocation failed: %d\n", ret);
|
||||
NV_PRINTK(err, cli, "Device allocation failed: %d\n", ret);
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = nvif_mclass(&cli->device.object, mmus);
|
||||
if (ret < 0) {
|
||||
NV_ERROR(drm, "No supported MMU class\n");
|
||||
NV_PRINTK(err, cli, "No supported MMU class\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = nvif_mmu_init(&cli->device.object, mmus[ret].oclass, &cli->mmu);
|
||||
if (ret) {
|
||||
NV_ERROR(drm, "MMU allocation failed: %d\n", ret);
|
||||
NV_PRINTK(err, cli, "MMU allocation failed: %d\n", ret);
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = nvif_mclass(&cli->mmu.object, vmms);
|
||||
if (ret < 0) {
|
||||
NV_ERROR(drm, "No supported VMM class\n");
|
||||
NV_PRINTK(err, cli, "No supported VMM class\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = nouveau_vmm_init(cli, vmms[ret].oclass, &cli->vmm);
|
||||
if (ret) {
|
||||
NV_ERROR(drm, "VMM allocation failed: %d\n", ret);
|
||||
NV_PRINTK(err, cli, "VMM allocation failed: %d\n", ret);
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = nvif_mclass(&cli->mmu.object, mems);
|
||||
if (ret < 0) {
|
||||
NV_ERROR(drm, "No supported MEM class\n");
|
||||
NV_PRINTK(err, cli, "No supported MEM class\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
@ -592,10 +592,8 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags)
|
||||
pm_runtime_allow(dev->dev);
|
||||
pm_runtime_mark_last_busy(dev->dev);
|
||||
pm_runtime_put(dev->dev);
|
||||
} else {
|
||||
/* enable polling for external displays */
|
||||
drm_kms_helper_poll_enable(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail_dispinit:
|
||||
@ -629,7 +627,7 @@ nouveau_drm_unload(struct drm_device *dev)
|
||||
nouveau_debugfs_fini(drm);
|
||||
|
||||
if (dev->mode_config.num_crtc)
|
||||
nouveau_display_fini(dev, false);
|
||||
nouveau_display_fini(dev, false, false);
|
||||
nouveau_display_destroy(dev);
|
||||
|
||||
nouveau_bios_takedown(dev);
|
||||
@ -835,7 +833,6 @@ nouveau_pmops_runtime_suspend(struct device *dev)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
drm_kms_helper_poll_disable(drm_dev);
|
||||
nouveau_switcheroo_optimus_dsm();
|
||||
ret = nouveau_do_suspend(drm_dev, true);
|
||||
pci_save_state(pdev);
|
||||
|
@ -466,6 +466,7 @@ nouveau_fbcon_set_suspend_work(struct work_struct *work)
|
||||
console_unlock();
|
||||
|
||||
if (state == FBINFO_STATE_RUNNING) {
|
||||
nouveau_fbcon_hotplug_resume(drm->fbcon);
|
||||
pm_runtime_mark_last_busy(drm->dev->dev);
|
||||
pm_runtime_put_sync(drm->dev->dev);
|
||||
}
|
||||
@ -487,6 +488,61 @@ nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
|
||||
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
|
||||
nouveau_fbcon_init(struct drm_device *dev)
|
||||
{
|
||||
@ -505,6 +561,7 @@ nouveau_fbcon_init(struct drm_device *dev)
|
||||
|
||||
drm->fbcon = fbcon;
|
||||
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);
|
||||
|
||||
|
@ -41,6 +41,9 @@ struct nouveau_fbdev {
|
||||
struct nvif_object gdi;
|
||||
struct nvif_object blit;
|
||||
struct nvif_object twod;
|
||||
|
||||
struct mutex hotplug_lock;
|
||||
bool hotplug_waiting;
|
||||
};
|
||||
|
||||
void nouveau_fbcon_restore(void);
|
||||
@ -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_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;
|
||||
|
||||
#endif /* __NV50_FBCON_H__ */
|
||||
|
@ -46,12 +46,10 @@ nouveau_switcheroo_set_state(struct pci_dev *pdev,
|
||||
pr_err("VGA switcheroo: switched nouveau on\n");
|
||||
dev->switch_power_state = DRM_SWITCH_POWER_CHANGING;
|
||||
nouveau_pmops_resume(&pdev->dev);
|
||||
drm_kms_helper_poll_enable(dev);
|
||||
dev->switch_power_state = DRM_SWITCH_POWER_ON;
|
||||
} else {
|
||||
pr_err("VGA switcheroo: switched nouveau off\n");
|
||||
dev->switch_power_state = DRM_SWITCH_POWER_CHANGING;
|
||||
drm_kms_helper_poll_disable(dev);
|
||||
nouveau_switcheroo_optimus_dsm();
|
||||
nouveau_pmops_suspend(&pdev->dev);
|
||||
dev->switch_power_state = DRM_SWITCH_POWER_OFF;
|
||||
|
@ -275,6 +275,7 @@ nvkm_disp_oneinit(struct nvkm_engine *engine)
|
||||
struct nvkm_outp *outp, *outt, *pair;
|
||||
struct nvkm_conn *conn;
|
||||
struct nvkm_head *head;
|
||||
struct nvkm_ior *ior;
|
||||
struct nvbios_connE connE;
|
||||
struct dcb_output dcbE;
|
||||
u8 hpd = 0, ver, hdr;
|
||||
@ -399,6 +400,19 @@ nvkm_disp_oneinit(struct nvkm_engine *engine)
|
||||
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;
|
||||
list_for_each_entry(head, &disp->head, head)
|
||||
i = max(i, head->id + 1);
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
#include <subdev/bios.h>
|
||||
#include <subdev/bios/init.h>
|
||||
#include <subdev/gpio.h>
|
||||
#include <subdev/i2c.h>
|
||||
|
||||
#include <nvif/event.h>
|
||||
@ -412,14 +413,10 @@ nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps)
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* 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. */
|
||||
nvbios_init(&ior->disp->engine.subdev, dp->info.script[4],
|
||||
init.outp = &dp->outp.info;
|
||||
@ -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
|
||||
nvkm_dp_acquire(struct nvkm_outp *outp)
|
||||
{
|
||||
@ -491,7 +498,7 @@ done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
static bool
|
||||
nvkm_dp_enable(struct nvkm_dp *dp, bool enable)
|
||||
{
|
||||
struct nvkm_i2c_aux *aux = dp->aux;
|
||||
@ -505,7 +512,7 @@ nvkm_dp_enable(struct nvkm_dp *dp, bool enable)
|
||||
|
||||
if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, dp->dpcd,
|
||||
sizeof(dp->dpcd)))
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dp->present) {
|
||||
@ -515,6 +522,7 @@ nvkm_dp_enable(struct nvkm_dp *dp, bool enable)
|
||||
}
|
||||
|
||||
atomic_set(&dp->lt.done, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
static int
|
||||
@ -555,9 +563,38 @@ nvkm_dp_fini(struct nvkm_outp *outp)
|
||||
static void
|
||||
nvkm_dp_init(struct nvkm_outp *outp)
|
||||
{
|
||||
struct nvkm_gpio *gpio = outp->disp->engine.subdev.device->gpio;
|
||||
struct nvkm_dp *dp = nvkm_dp(outp);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -576,6 +613,7 @@ nvkm_dp_func = {
|
||||
.fini = nvkm_dp_fini,
|
||||
.acquire = nvkm_dp_acquire,
|
||||
.release = nvkm_dp_release,
|
||||
.disable = nvkm_dp_disable,
|
||||
};
|
||||
|
||||
static int
|
||||
|
@ -16,6 +16,7 @@ struct nvkm_ior {
|
||||
char name[8];
|
||||
|
||||
struct list_head head;
|
||||
bool identity;
|
||||
|
||||
struct nvkm_ior_state {
|
||||
struct nvkm_outp *outp;
|
||||
|
@ -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);
|
||||
|
||||
/* 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 ((outp = ior->arm.outp) && outp->func->release)
|
||||
outp->func->release(outp, ior);
|
||||
if ((outp = ior->arm.outp) && outp->func->disable)
|
||||
outp->func->disable(outp, ior);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,6 +93,8 @@ nvkm_outp_release(struct nvkm_outp *outp, u8 user)
|
||||
if (ior) {
|
||||
outp->acquired &= ~user;
|
||||
if (!outp->acquired) {
|
||||
if (outp->func->release && outp->ior)
|
||||
outp->func->release(outp);
|
||||
outp->ior->asy.outp = NULL;
|
||||
outp->ior = NULL;
|
||||
}
|
||||
@ -127,17 +129,26 @@ nvkm_outp_acquire(struct nvkm_outp *outp, u8 user)
|
||||
if (proto == UNKNOWN)
|
||||
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
|
||||
* on HW, if any, in order to prevent unnecessary switching.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/* Failing that, a completely unused OR is the next best thing. */
|
||||
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)))
|
||||
return nvkm_outp_acquire_ior(outp, user, ior);
|
||||
}
|
||||
@ -146,7 +157,7 @@ nvkm_outp_acquire(struct nvkm_outp *outp, u8 user)
|
||||
* but will be released during the next modeset.
|
||||
*/
|
||||
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)))
|
||||
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,
|
||||
outp->index = index;
|
||||
outp->info = *dcbE;
|
||||
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 "
|
||||
"edid %x bus %d head %x",
|
||||
|
@ -13,10 +13,10 @@ struct nvkm_outp {
|
||||
struct dcb_output info;
|
||||
|
||||
struct nvkm_i2c_bus *i2c;
|
||||
int or;
|
||||
|
||||
struct list_head head;
|
||||
struct nvkm_conn *conn;
|
||||
bool identity;
|
||||
|
||||
/* Assembly state. */
|
||||
#define NVKM_OUTP_PRIV 1
|
||||
@ -41,7 +41,8 @@ struct nvkm_outp_func {
|
||||
void (*init)(struct nvkm_outp *);
|
||||
void (*fini)(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 { \
|
||||
|
@ -158,7 +158,8 @@ gm200_devinit_post(struct nvkm_devinit *base, bool post)
|
||||
}
|
||||
|
||||
/* 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
|
||||
|
@ -1423,7 +1423,7 @@ nvkm_vmm_get(struct nvkm_vmm *vmm, u8 page, u64 size, struct nvkm_vma **pvma)
|
||||
void
|
||||
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);
|
||||
vmm->func->part(vmm, inst);
|
||||
mutex_unlock(&vmm->mutex);
|
||||
|
Loading…
Reference in New Issue
Block a user