Merge branch 'linux-4.19' of git://github.com/skeggsb/linux into drm-fixes
A bunch of fixes for MST/runpm problems and races, as well as fixes for issues that prevent more recent laptops from booting. Signed-off-by: Dave Airlie <airlied@redhat.com> From: Ben Skeggs <bskeggs@redhat.com> Link: https://patchwork.freedesktop.org/patch/msgid/CABDvA==GF63dy8a9j611=-0x8G6FRu7uC-ZQypsLO_hqV4OAcA@mail.gmail.com
This commit is contained in:
commit
2887e5ce15
|
@ -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