From 48a92916729bd0ed6bc1d938557cd3779a0657fd Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Thu, 11 May 2017 16:10:42 -0300 Subject: [PATCH 01/34] drm/exynos: use drm_for_each_connector_iter() Drop legacy drm_for_each_connector() in favor of the race-free drm_for_each_connector_iter() Cc: Inki Dae Cc: Joonyoung Shim Signed-off-by: Gustavo Padovan Reviewed-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170511191049.28944-2-gustavo@padovan.org --- drivers/gpu/drm/exynos/exynos_drm_drv.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 50294a7bd29da..99f0e67694d21 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -171,12 +171,13 @@ static int exynos_drm_suspend(struct device *dev) { struct drm_device *drm_dev = dev_get_drvdata(dev); struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; if (pm_runtime_suspended(dev) || !drm_dev) return 0; - drm_modeset_lock_all(drm_dev); - drm_for_each_connector(connector, drm_dev) { + drm_connector_list_iter_begin(drm_dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { int old_dpms = connector->dpms; if (connector->funcs->dpms) @@ -185,7 +186,7 @@ static int exynos_drm_suspend(struct device *dev) /* Set the old mode back to the connector for resume */ connector->dpms = old_dpms; } - drm_modeset_unlock_all(drm_dev); + drm_connector_list_iter_end(&conn_iter); return 0; } @@ -194,12 +195,13 @@ static int exynos_drm_resume(struct device *dev) { struct drm_device *drm_dev = dev_get_drvdata(dev); struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; if (pm_runtime_suspended(dev) || !drm_dev) return 0; - drm_modeset_lock_all(drm_dev); - drm_for_each_connector(connector, drm_dev) { + drm_connector_list_iter_begin(drm_dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { if (connector->funcs->dpms) { int dpms = connector->dpms; @@ -207,7 +209,7 @@ static int exynos_drm_resume(struct device *dev) connector->funcs->dpms(connector, dpms); } } - drm_modeset_unlock_all(drm_dev); + drm_connector_list_iter_end(&conn_iter); return 0; } From 2cbeb64f6c709de67e2ba5b97ff45d2b6147b286 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Mon, 15 May 2017 10:43:30 -0300 Subject: [PATCH 02/34] drm/rockchip: use drm_for_each_connector_iter() Drop legacy drm_for_each_connector() in favor of the race-free drm_for_each_connector_iter() Cc: Mark Yao Signed-off-by: Gustavo Padovan Reviewed-by: Daniel Vetter Reviewed-by: Sean Paul Link: http://patchwork.freedesktop.org/patch/msgid/20170515134330.3275-1-gustavo@padovan.org --- drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index 40a5e6ef6f2c8..9b3525a369697 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -1118,16 +1118,17 @@ static void vop_crtc_destroy_state(struct drm_crtc *crtc, #ifdef CONFIG_DRM_ANALOGIX_DP static struct drm_connector *vop_get_edp_connector(struct vop *vop) { - struct drm_crtc *crtc = &vop->crtc; struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; - mutex_lock(&crtc->dev->mode_config.mutex); - drm_for_each_connector(connector, crtc->dev) + drm_connector_list_iter_begin(vop->drm_dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { - mutex_unlock(&crtc->dev->mode_config.mutex); + drm_connector_list_iter_end(&conn_iter); return connector; } - mutex_unlock(&crtc->dev->mode_config.mutex); + } + drm_connector_list_iter_end(&conn_iter); return NULL; } From 2fd96b4116bfe922b4a1cf7626b30f5c2010c218 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Thu, 11 May 2017 16:10:44 -0300 Subject: [PATCH 03/34] drm/i915: use drm_for_each_connector_iter() Drop legacy drm_for_each_connector() in favor of the race-free drm_for_each_connector_iter(). Cc: Daniel Vetter Cc: Jani Nikula Signed-off-by: Gustavo Padovan Reviewed-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170511191049.28944-4-gustavo@padovan.org --- drivers/gpu/drm/i915/intel_display.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 6a037b856d967..e6e26705c138a 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -11216,6 +11216,7 @@ static bool check_digital_port_conflicts(struct drm_atomic_state *state) { struct drm_device *dev = state->dev; struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; unsigned int used_ports = 0; unsigned int used_mst_ports = 0; @@ -11224,7 +11225,8 @@ static bool check_digital_port_conflicts(struct drm_atomic_state *state) * list to detect the problem on ddi platforms * where there's just one encoder per digital port. */ - drm_for_each_connector(connector, dev) { + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { struct drm_connector_state *connector_state; struct intel_encoder *encoder; @@ -11263,6 +11265,7 @@ static bool check_digital_port_conflicts(struct drm_atomic_state *state) break; } } + drm_connector_list_iter_end(&conn_iter); /* can't mix MST and SST/HDMI on the same port */ if (used_ports & used_mst_ports) From c8bf8b968adb0c14a175a5c2e091f620b9771870 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Thu, 11 May 2017 16:10:45 -0300 Subject: [PATCH 04/34] drm/mediatek: use drm_for_each_connector_iter() Drop legacy drm_for_each_connector() in favor of the race-free drm_for_each_connector_iter(). Cc: Philipp Zabel Signed-off-by: Gustavo Padovan Reviewed-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170511191049.28944-5-gustavo@padovan.org --- drivers/gpu/drm/mediatek/mtk_drm_crtc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/mediatek/mtk_drm_crtc.c b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c index 6b08774e5501a..6582e1f56d37c 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_crtc.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_crtc.c @@ -221,6 +221,7 @@ static int mtk_crtc_ddp_hw_init(struct mtk_drm_crtc *mtk_crtc) struct drm_crtc *crtc = &mtk_crtc->base; struct drm_connector *connector; struct drm_encoder *encoder; + struct drm_connector_list_iter conn_iter; unsigned int width, height, vrefresh, bpc = MTK_MAX_BPC; int ret; int i; @@ -237,13 +238,15 @@ static int mtk_crtc_ddp_hw_init(struct mtk_drm_crtc *mtk_crtc) if (encoder->crtc != crtc) continue; - drm_for_each_connector(connector, crtc->dev) { + drm_connector_list_iter_begin(crtc->dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { if (connector->encoder != encoder) continue; if (connector->display_info.bpc != 0 && bpc > connector->display_info.bpc) bpc = connector->display_info.bpc; } + drm_connector_list_iter_end(&conn_iter); } ret = pm_runtime_get_sync(crtc->dev->dev); From 875dd62697fe22d54780980d26089c43e7b9b446 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Thu, 11 May 2017 16:10:46 -0300 Subject: [PATCH 05/34] drm/nouveau: use drm_for_each_connector_iter() Drop legacy drm_for_each_connector() in favor of the race-free drm_for_each_connector_iter(). Cc: Ben Skeggs Signed-off-by: Gustavo Padovan Reviewed-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170511191049.28944-6-gustavo@padovan.org --- drivers/gpu/drm/nouveau/nv50_display.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c index 9303daa79aba1..e9189e59216ba 100644 --- a/drivers/gpu/drm/nouveau/nv50_display.c +++ b/drivers/gpu/drm/nouveau/nv50_display.c @@ -2872,17 +2872,20 @@ nv50_msto_enable(struct drm_encoder *encoder) struct nv50_mstc *mstc = NULL; struct nv50_mstm *mstm = NULL; struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; u8 proto, depth; int slots; bool r; - drm_for_each_connector(connector, encoder->dev) { + drm_connector_list_iter_begin(encoder->dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { if (connector->state->best_encoder == &msto->encoder) { mstc = nv50_mstc(connector); mstm = mstc->mstm; break; } } + drm_connector_list_iter_end(&conn_iter); if (WARN_ON(!mstc)) return; From 4894bf711d19a1bdcc954e0ed4971d6c0b399b90 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Fri, 12 May 2017 13:41:00 -0300 Subject: [PATCH 06/34] drm/vc4: use drm_for_each_connector_iter() Drop legacy drm_for_each_connector() in favor of the race-free drm_for_each_connector_iter(). v2: add missing drm_connector_list_iter_end(Daniel Vetter) Cc: Eric Anholt Signed-off-by: Gustavo Padovan Reviewed-by: Daniel Vetter Acked-by: Eric Anholt Link: http://patchwork.freedesktop.org/patch/msgid/20170512164100.7649-1-gustavo@padovan.org --- drivers/gpu/drm/vc4/vc4_crtc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 0bfc4d88e4c23..403bbd5f99a9f 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -345,12 +345,16 @@ static u32 vc4_get_fifo_full_level(u32 format) static struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc) { struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; - drm_for_each_connector(connector, crtc->dev) { + drm_connector_list_iter_begin(crtc->dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { if (connector->state->crtc == crtc) { + drm_connector_list_iter_end(&conn_iter); return connector->encoder; } } + drm_connector_list_iter_end(&conn_iter); return NULL; } From eb53a46be597b5f413fbcab9df0a3422bbb3fc16 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Thu, 11 May 2017 16:10:48 -0300 Subject: [PATCH 07/34] drm: remove unsafe drm_for_each_connector() After converting all users to drm_for_each_connector_iter() we no longer need drm_for_each_connector() so we can go ahead and remove it. Signed-off-by: Gustavo Padovan Reviewed-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170511191049.28944-8-gustavo@padovan.org --- include/drm/drm_connector.h | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 2fe09c1ddfb81..d8bb25f38ebaa 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1009,21 +1009,6 @@ struct drm_tile_group *drm_mode_get_tile_group(struct drm_device *dev, void drm_mode_put_tile_group(struct drm_device *dev, struct drm_tile_group *tg); -/** - * drm_for_each_connector - iterate over all connectors - * @connector: the loop cursor - * @dev: the DRM device - * - * Iterate over all connectors of @dev. - * - * WARNING: - * - * This iterator is not safe against hotadd/removal of connectors and is - * deprecated. Use drm_for_each_connector_iter() instead. - */ -#define drm_for_each_connector(connector, dev) \ - list_for_each_entry(connector, &(dev)->mode_config.connector_list, head) - /** * struct drm_connector_list_iter - connector_list iterator * From a301b11186151271c20ecebcc1e2ce1928832c93 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Thu, 11 May 2017 16:10:49 -0300 Subject: [PATCH 08/34] drm: todo: remove task about switch to drm_connector_list_iter This is now completed. Signed-off-by: Gustavo Padovan Reviewed-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170511191049.28944-9-gustavo@padovan.org --- Documentation/gpu/todo.rst | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst index 1bdb7356a3102..95a517077eb02 100644 --- a/Documentation/gpu/todo.rst +++ b/Documentation/gpu/todo.rst @@ -177,19 +177,6 @@ following drivers still use ``struct_mutex``: ``msm``, ``omapdrm`` and Contact: Daniel Vetter, respective driver maintainers -Switch to drm_connector_list_iter for any connector_list walking ----------------------------------------------------------------- - -Connectors can be hotplugged, and we now have a special list of helpers to walk -the connector_list in a race-free fashion, without incurring deadlocks on -mutexes and other fun stuff. - -Unfortunately most drivers are not converted yet. At least all those supporting -DP MST hotplug should be converted, since for those drivers the difference -matters. See drm_for_each_connector_iter() vs. drm_for_each_connector(). - -Contact: Daniel Vetter - Core refactorings ================= From 18e51064c42ca3945b94dd4652156b62457962bc Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 24 May 2017 16:51:37 +0200 Subject: [PATCH 09/34] drm: Remove drm_device->virtdev This is a leftover from the drm_bus days, where we've had a bus-specific device type for every bus type in drm_device. Except for pci (which we can't remove because dri1 drivers) this is all gone. And the virt driver also doesn't really need it, dev_to_virtio works perfectly fine. Cc: David Airlie Cc: Gerd Hoffmann Cc: virtualization@lists.linux-foundation.org Reviewed-by: Gerd Hoffmann Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-3-daniel.vetter@ffwll.ch --- drivers/gpu/drm/virtio/virtgpu_drm_bus.c | 1 - drivers/gpu/drm/virtio/virtgpu_kms.c | 4 ++-- include/drm/drmP.h | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_drm_bus.c b/drivers/gpu/drm/virtio/virtgpu_drm_bus.c index 43e1d5916c6c6..7df8d0c9026ac 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drm_bus.c +++ b/drivers/gpu/drm/virtio/virtgpu_drm_bus.c @@ -56,7 +56,6 @@ int drm_virtio_init(struct drm_driver *driver, struct virtio_device *vdev) dev = drm_dev_alloc(driver, &vdev->dev); if (IS_ERR(dev)) return PTR_ERR(dev); - dev->virtdev = vdev; vdev->priv = dev; if (strcmp(vdev->dev.parent->bus->name, "pci") == 0) { diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c index 1e1c90b30d4ad..6400506a06b07 100644 --- a/drivers/gpu/drm/virtio/virtgpu_kms.c +++ b/drivers/gpu/drm/virtio/virtgpu_kms.c @@ -138,7 +138,7 @@ int virtio_gpu_driver_load(struct drm_device *dev, unsigned long flags) u32 num_scanouts, num_capsets; int ret; - if (!virtio_has_feature(dev->virtdev, VIRTIO_F_VERSION_1)) + if (!virtio_has_feature(dev_to_virtio(dev->dev), VIRTIO_F_VERSION_1)) return -ENODEV; vgdev = kzalloc(sizeof(struct virtio_gpu_device), GFP_KERNEL); @@ -147,7 +147,7 @@ int virtio_gpu_driver_load(struct drm_device *dev, unsigned long flags) vgdev->ddev = dev; dev->dev_private = vgdev; - vgdev->vdev = dev->virtdev; + vgdev->vdev = dev_to_virtio(dev->dev); vgdev->dev = dev->dev; spin_lock_init(&vgdev->display_info_lock); diff --git a/include/drm/drmP.h b/include/drm/drmP.h index b9b5566acfe60..3f724c53c9ed4 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -429,8 +429,6 @@ struct drm_device { struct pci_controller *hose; #endif - struct virtio_device *virtdev; - struct drm_sg_mem *sg; /**< Scatter gather memory */ unsigned int num_crtcs; /**< Number of CRTCs on this device */ From da251bf8f93513519a3c18b2b2290557ea5ba860 Mon Sep 17 00:00:00 2001 From: Jose Abreu Date: Thu, 25 May 2017 15:19:13 +0100 Subject: [PATCH 10/34] drm: Add drm_{crtc/encoder/connector}_mode_valid() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new helper to call crtc->mode_valid, connector->mode_valid and encoder->mode_valid callbacks. Suggested-by: Ville Syrjälä Signed-off-by: Jose Abreu Reviewed-by: Daniel Vetter Reviewed-by: Andrzej Hajda Cc: Carlos Palminha Cc: Dave Airlie Changes v2->v3: - Move helpers to drm_probe_helper.c (Daniel) - Squeeze patches that introduce helpers into a single one (Daniel) Signed-off-by: Jose Abreu [danvet: Make it compile when CONFIG_DRM_DP_AUX_CHARDEV is selected.] Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/b55c8bd029da219ff04e39086025c115731a49b1.1495720737.git.joabreu@synopsys.com --- drivers/gpu/drm/drm_crtc_helper_internal.h | 12 +++++++ drivers/gpu/drm/drm_probe_helper.c | 38 ++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/drivers/gpu/drm/drm_crtc_helper_internal.h b/drivers/gpu/drm/drm_crtc_helper_internal.h index 28295e5d0d9ea..b5ac1581e6231 100644 --- a/drivers/gpu/drm/drm_crtc_helper_internal.h +++ b/drivers/gpu/drm/drm_crtc_helper_internal.h @@ -26,7 +26,11 @@ * implementation details and are not exported to drivers. */ +#include +#include #include +#include +#include /* drm_fb_helper.c */ #ifdef CONFIG_DRM_FBDEV_EMULATION @@ -63,3 +67,11 @@ static inline void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) { } #endif + +/* drm_probe_helper.c */ +enum drm_mode_status drm_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode); +enum drm_mode_status drm_encoder_mode_valid(struct drm_encoder *encoder, + const struct drm_display_mode *mode); +enum drm_mode_status drm_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode); diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index 1b0c14ab3fffd..f01abdc124d28 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -38,6 +38,9 @@ #include #include #include +#include + +#include "drm_crtc_helper_internal.h" /** * DOC: output probing helper overview @@ -113,6 +116,41 @@ static int drm_helper_probe_add_cmdline_mode(struct drm_connector *connector) return 1; } +enum drm_mode_status drm_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + const struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; + + if (!crtc_funcs || !crtc_funcs->mode_valid) + return MODE_OK; + + return crtc_funcs->mode_valid(crtc, mode); +} + +enum drm_mode_status drm_encoder_mode_valid(struct drm_encoder *encoder, + const struct drm_display_mode *mode) +{ + const struct drm_encoder_helper_funcs *encoder_funcs = + encoder->helper_private; + + if (!encoder_funcs || !encoder_funcs->mode_valid) + return MODE_OK; + + return encoder_funcs->mode_valid(encoder, mode); +} + +enum drm_mode_status drm_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + const struct drm_connector_helper_funcs *connector_funcs = + connector->helper_private; + + if (!connector_funcs || !connector_funcs->mode_valid) + return MODE_OK; + + return connector_funcs->mode_valid(connector, mode); +} + #define DRM_OUTPUT_POLL_PERIOD (10*HZ) /** * drm_kms_helper_poll_enable - re-enable output polling. From b1240f81a1865670e44d25593e47c2023a20ab62 Mon Sep 17 00:00:00 2001 From: Jose Abreu Date: Thu, 25 May 2017 15:19:14 +0100 Subject: [PATCH 11/34] drm: Introduce drm_bridge_mode_valid() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a new helper function which calls mode_valid() callback for all bridges in an encoder chain. Signed-off-by: Jose Abreu Reviewed-by: Daniel Vetter Cc: Carlos Palminha Cc: Ville Syrjälä Cc: Dave Airlie Cc: Andrzej Hajda Cc: Archit Taneja Cc: Laurent Pinchart Reviewed-by: Archit Taneja Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/36bd5e054496ad3c9c71f1ffe204f28533f55f1e.1495720737.git.joabreu@synopsys.com --- drivers/gpu/drm/drm_bridge.c | 33 +++++++++++++++++++++++++++++++++ include/drm/drm_bridge.h | 2 ++ 2 files changed, 35 insertions(+) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 86a7637ba344c..dc8cdfe1dcacf 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -205,6 +205,39 @@ bool drm_bridge_mode_fixup(struct drm_bridge *bridge, } EXPORT_SYMBOL(drm_bridge_mode_fixup); +/** + * drm_bridge_mode_valid - validate the mode against all bridges in the + * encoder chain. + * @bridge: bridge control structure + * @mode: desired mode to be validated + * + * Calls &drm_bridge_funcs.mode_valid for all the bridges in the encoder + * chain, starting from the first bridge to the last. If at least one bridge + * does not accept the mode the function returns the error code. + * + * Note: the bridge passed should be the one closest to the encoder. + * + * RETURNS: + * MODE_OK on success, drm_mode_status Enum error code on failure + */ +enum drm_mode_status drm_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_mode *mode) +{ + enum drm_mode_status ret = MODE_OK; + + if (!bridge) + return ret; + + if (bridge->funcs->mode_valid) + ret = bridge->funcs->mode_valid(bridge, mode); + + if (ret != MODE_OK) + return ret; + + return drm_bridge_mode_valid(bridge->next, mode); +} +EXPORT_SYMBOL(drm_bridge_mode_valid); + /** * drm_bridge_disable - disables all bridges in the encoder chain * @bridge: bridge control structure diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 983054f2e86ec..5b106eca6d573 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -253,6 +253,8 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge, bool drm_bridge_mode_fixup(struct drm_bridge *bridge, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode); +enum drm_mode_status drm_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_mode *mode); void drm_bridge_disable(struct drm_bridge *bridge); void drm_bridge_post_disable(struct drm_bridge *bridge); void drm_bridge_mode_set(struct drm_bridge *bridge, From 97f973d652602425f4dc31a13e8605d5841afe25 Mon Sep 17 00:00:00 2001 From: Jose Abreu Date: Thu, 25 May 2017 15:19:15 +0100 Subject: [PATCH 12/34] drm: Use new mode_valid() helpers in connector probe helper This changes the connector probe helper function to use the new encoder->mode_valid(), bridge->mode_valid() and crtc->mode_valid() helper callbacks to validate the modes. The new callbacks are optional so the behaviour remains the same if they are not implemented. If they are, then the code loops through all the connector's encodersXbridgesXcrtcs and calls the callback. If at least a valid encoderXbridgeXcrtc combination is found which accepts the mode then the function returns MODE_OK. Signed-off-by: Jose Abreu Reviewed-by: Daniel Vetter Cc: Carlos Palminha Cc: Dave Airlie Cc: Andrzej Hajda Cc: Laurent Pinchart Changes v3->v4: - Change function name (Laurent) Changes v2->v3: - Call also bridge->mode_valid (Daniel) Changes v1->v2: - Use new helpers suggested by Ville - Change documentation (Daniel) Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/d4e3ba87d822fa92f1b8773e441b9a02af3bde71.1495720737.git.joabreu@synopsys.com --- drivers/gpu/drm/drm_probe_helper.c | 67 ++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index f01abdc124d28..00e6832a8c1a6 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -83,6 +83,61 @@ drm_mode_validate_flag(const struct drm_display_mode *mode, return MODE_OK; } +static enum drm_mode_status +drm_mode_validate_pipeline(struct drm_display_mode *mode, + struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + uint32_t *ids = connector->encoder_ids; + enum drm_mode_status ret = MODE_OK; + unsigned int i; + + /* Step 1: Validate against connector */ + ret = drm_connector_mode_valid(connector, mode); + if (ret != MODE_OK) + return ret; + + /* Step 2: Validate against encoders and crtcs */ + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + struct drm_encoder *encoder = drm_encoder_find(dev, ids[i]); + struct drm_crtc *crtc; + + if (!encoder) + continue; + + ret = drm_encoder_mode_valid(encoder, mode); + if (ret != MODE_OK) { + /* No point in continuing for crtc check as this encoder + * will not accept the mode anyway. If all encoders + * reject the mode then, at exit, ret will not be + * MODE_OK. */ + continue; + } + + ret = drm_bridge_mode_valid(encoder->bridge, mode); + if (ret != MODE_OK) { + /* There is also no point in continuing for crtc check + * here. */ + continue; + } + + drm_for_each_crtc(crtc, dev) { + if (!drm_encoder_crtc_ok(encoder, crtc)) + continue; + + ret = drm_crtc_mode_valid(crtc, mode); + if (ret == MODE_OK) { + /* If we get to this point there is at least + * one combination of encoder+crtc that works + * for this mode. Lets return now. */ + return ret; + } + } + } + + return ret; +} + static int drm_helper_probe_add_cmdline_mode(struct drm_connector *connector) { struct drm_cmdline_mode *cmdline_mode; @@ -322,7 +377,11 @@ EXPORT_SYMBOL(drm_helper_probe_detect); * - drm_mode_validate_flag() checks the modes against basic connector * capabilities (interlace_allowed,doublescan_allowed,stereo_allowed) * - the optional &drm_connector_helper_funcs.mode_valid helper can perform - * driver and/or hardware specific checks + * driver and/or sink specific checks + * - the optional &drm_crtc_helper_funcs.mode_valid, + * &drm_bridge_funcs.mode_valid and &drm_encoder_helper_funcs.mode_valid + * helpers can perform driver and/or source specific checks which are also + * enforced by the modeset/atomic helpers * * 5. Any mode whose status is not OK is pruned from the connector's modes list, * accompanied by a debug message indicating the reason for the mode's @@ -466,9 +525,9 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector, if (mode->status == MODE_OK) mode->status = drm_mode_validate_flag(mode, mode_flags); - if (mode->status == MODE_OK && connector_funcs->mode_valid) - mode->status = connector_funcs->mode_valid(connector, - mode); + if (mode->status == MODE_OK) + mode->status = drm_mode_validate_pipeline(mode, + connector); } prune: From faf94a0883cedce892ef10a258c9388929462525 Mon Sep 17 00:00:00 2001 From: Jose Abreu Date: Thu, 25 May 2017 15:19:16 +0100 Subject: [PATCH 13/34] drm: Use mode_valid() in atomic modeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patches makes use of the new mode_valid() callbacks introduced previously to validate the full video pipeline when modesetting. This calls the connector->mode_valid(), encoder->mode_valid(), bridge->mode_valid() and crtc->mode_valid() so that we can make sure that the mode will be accepted in every components. Signed-off-by: Jose Abreu Reviewed-by: Daniel Vetter Reviewed-by: Andrzej Hajda Cc: Carlos Palminha Cc: Ville Syrjälä Cc: Daniel Vetter Cc: Dave Airlie Cc: Andrzej Hajda Cc: Archit Taneja Cc: Laurent Pinchart Changes v1->v2: - Removed call to connector->mode_valid (Ville, Daniel) - Changed function name (Ville) - Use for_each_new_connector_in_state (Ville) - Do not validate if connector and mode didn't change (Ville) - Use new helpers to call mode_valid Signed-off-by: Jose Abreu Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/a457d6a69ad07b3936304653c919068c430c0857.1495720737.git.joabreu@synopsys.com --- drivers/gpu/drm/drm_atomic_helper.c | 76 +++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 636e561486a8f..93b0221d5d0fd 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -32,6 +32,7 @@ #include #include +#include "drm_crtc_helper_internal.h" #include "drm_crtc_internal.h" /** @@ -452,6 +453,69 @@ mode_fixup(struct drm_atomic_state *state) return 0; } +static enum drm_mode_status mode_valid_path(struct drm_connector *connector, + struct drm_encoder *encoder, + struct drm_crtc *crtc, + struct drm_display_mode *mode) +{ + enum drm_mode_status ret; + + ret = drm_encoder_mode_valid(encoder, mode); + if (ret != MODE_OK) { + DRM_DEBUG_ATOMIC("[ENCODER:%d:%s] mode_valid() failed\n", + encoder->base.id, encoder->name); + return ret; + } + + ret = drm_bridge_mode_valid(encoder->bridge, mode); + if (ret != MODE_OK) { + DRM_DEBUG_ATOMIC("[BRIDGE] mode_valid() failed\n"); + return ret; + } + + ret = drm_crtc_mode_valid(crtc, mode); + if (ret != MODE_OK) { + DRM_DEBUG_ATOMIC("[CRTC:%d:%s] mode_valid() failed\n", + crtc->base.id, crtc->name); + return ret; + } + + return ret; +} + +static int +mode_valid(struct drm_atomic_state *state) +{ + struct drm_connector_state *conn_state; + struct drm_connector *connector; + int i; + + for_each_new_connector_in_state(state, connector, conn_state, i) { + struct drm_encoder *encoder = conn_state->best_encoder; + struct drm_crtc *crtc = conn_state->crtc; + struct drm_crtc_state *crtc_state; + enum drm_mode_status mode_status; + struct drm_display_mode *mode; + + if (!crtc || !encoder) + continue; + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + if (!crtc_state) + continue; + if (!crtc_state->mode_changed && !crtc_state->connectors_changed) + continue; + + mode = &crtc_state->mode; + + mode_status = mode_valid_path(connector, encoder, crtc, mode); + if (mode_status != MODE_OK) + return -EINVAL; + } + + return 0; +} + /** * drm_atomic_helper_check_modeset - validate state object for modeset changes * @dev: DRM device @@ -466,13 +530,15 @@ mode_fixup(struct drm_atomic_state *state) * 2. &drm_connector_helper_funcs.atomic_check to validate the connector state. * 3. If it's determined a modeset is needed then all connectors on the affected crtc * crtc are added and &drm_connector_helper_funcs.atomic_check is run on them. - * 4. &drm_bridge_funcs.mode_fixup is called on all encoder bridges. - * 5. &drm_encoder_helper_funcs.atomic_check is called to validate any encoder state. + * 4. &drm_encoder_helper_funcs.mode_valid, &drm_bridge_funcs.mode_valid and + * &drm_crtc_helper_funcs.mode_valid are called on the affected components. + * 5. &drm_bridge_funcs.mode_fixup is called on all encoder bridges. + * 6. &drm_encoder_helper_funcs.atomic_check is called to validate any encoder state. * This function is only called when the encoder will be part of a configured crtc, * it must not be used for implementing connector property validation. * If this function is NULL, &drm_atomic_encoder_helper_funcs.mode_fixup is called * instead. - * 6. &drm_crtc_helper_funcs.mode_fixup is called last, to fix up the mode with crtc constraints. + * 7. &drm_crtc_helper_funcs.mode_fixup is called last, to fix up the mode with crtc constraints. * * &drm_crtc_state.mode_changed is set when the input mode is changed. * &drm_crtc_state.connectors_changed is set when a connector is added or @@ -617,6 +683,10 @@ drm_atomic_helper_check_modeset(struct drm_device *dev, return ret; } + ret = mode_valid(state); + if (ret) + return ret; + return mode_fixup(state); } EXPORT_SYMBOL(drm_atomic_helper_check_modeset); From 7dbcbce9f55141d775c0a227e55bc78eb1ac672b Mon Sep 17 00:00:00 2001 From: Jose Abreu Date: Thu, 25 May 2017 15:19:18 +0100 Subject: [PATCH 14/34] drm/bridge: analogix-anx78xx: Use bridge->mode_valid() callback Now that we have a callback to check if bridge supports a given mode we can use it in Analogix bridge so that we restrict the number of probbed modes to the ones we can actually display. Also, there is no need to use mode_fixup() callback as mode_valid() will handle the mode validation. Reviewed-by: Neil Armstrong Signed-off-by: Jose Abreu Cc: Carlos Palminha Cc: Daniel Vetter Cc: Archit Taneja Cc: Andrzej Hajda Cc: Laurent Pinchart Cc: David Airlie Signed-off-by: Archit Taneja Link: http://patchwork.freedesktop.org/patch/msgid/1d0ed1858ae56c827bd09cc1fa6ff4a05d1530eb.1495720737.git.joabreu@synopsys.com --- drivers/gpu/drm/bridge/analogix-anx78xx.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/bridge/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix-anx78xx.c index a2a82366a7714..9006578b9789b 100644 --- a/drivers/gpu/drm/bridge/analogix-anx78xx.c +++ b/drivers/gpu/drm/bridge/analogix-anx78xx.c @@ -1061,18 +1061,18 @@ static int anx78xx_bridge_attach(struct drm_bridge *bridge) return 0; } -static bool anx78xx_bridge_mode_fixup(struct drm_bridge *bridge, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) +static enum drm_mode_status +anx78xx_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_mode *mode) { if (mode->flags & DRM_MODE_FLAG_INTERLACE) - return false; + return MODE_NO_INTERLACE; /* Max 1200p at 5.4 Ghz, one lane */ if (mode->clock > 154000) - return false; + return MODE_CLOCK_HIGH; - return true; + return MODE_OK; } static void anx78xx_bridge_disable(struct drm_bridge *bridge) @@ -1129,7 +1129,7 @@ static void anx78xx_bridge_enable(struct drm_bridge *bridge) static const struct drm_bridge_funcs anx78xx_bridge_funcs = { .attach = anx78xx_bridge_attach, - .mode_fixup = anx78xx_bridge_mode_fixup, + .mode_valid = anx78xx_bridge_mode_valid, .disable = anx78xx_bridge_disable, .mode_set = anx78xx_bridge_mode_set, .enable = anx78xx_bridge_enable, From e7cc23de3e4e9222ae4fd24a00583aa5d2ed7610 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 30 May 2017 11:22:08 +0200 Subject: [PATCH 15/34] drm/pl111: select DRM_PANEL When DRM_PANEL is disabled, we get a link error for pl111: drivers/gpu/built-in.o: In function `pl111_connector_destroy': pl111_connector.c:(.text+0x3487e6): undefined reference to `drm_panel_detach' For some reason this only appears in the latest linux-next although the driver appears to have used the symbol for a few weeks already. The solution however is simple enough, we just need to add a 'select' statement. Fixes: bed41005e617 ("drm/pl111: Initial drm/kms driver for pl111") Signed-off-by: Arnd Bergmann Tested-by: Linus Walleij Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170530092224.1204037-1-arnd@arndb.de --- drivers/gpu/drm/pl111/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/pl111/Kconfig b/drivers/gpu/drm/pl111/Kconfig index 309f4fd52de78..bbfba87cd1a88 100644 --- a/drivers/gpu/drm/pl111/Kconfig +++ b/drivers/gpu/drm/pl111/Kconfig @@ -6,6 +6,7 @@ config DRM_PL111 select DRM_KMS_HELPER select DRM_KMS_CMA_HELPER select DRM_GEM_CMA_HELPER + select DRM_PANEL select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE help Choose this option for DRM support for the PL111 CLCD controller. From 99cdb35e787b277bc66ed9fe7d5f9852de9e0eb0 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 24 May 2017 16:51:36 +0200 Subject: [PATCH 16/34] drm/doc: move printf helpers out of drmP.h And document them lightly. Unfortunately kernel-doc isn't the most awesome for documenting #defines that don't look like functions, it makes functions out of them :-/ Signed-off-by: Daniel Vetter Reviewed-by: Neil Armstrong Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-2-daniel.vetter@ffwll.ch Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-3-daniel.vetter@ffwll.ch --- include/drm/drmP.h | 17 ----------------- include/drm/drm_modes.h | 17 +++++++++++++++++ include/drm/drm_rect.h | 27 +++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 3f724c53c9ed4..c363f2fdff31f 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -292,23 +292,6 @@ struct pci_controller; /* Format strings and argument splitters to simplify printing * various "complex" objects */ -#define DRM_MODE_FMT "%d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x" -#define DRM_MODE_ARG(m) \ - (m)->base.id, (m)->name, (m)->vrefresh, (m)->clock, \ - (m)->hdisplay, (m)->hsync_start, (m)->hsync_end, (m)->htotal, \ - (m)->vdisplay, (m)->vsync_start, (m)->vsync_end, (m)->vtotal, \ - (m)->type, (m)->flags - -#define DRM_RECT_FMT "%dx%d%+d%+d" -#define DRM_RECT_ARG(r) drm_rect_width(r), drm_rect_height(r), (r)->x1, (r)->y1 - -/* for rect's in fixed-point format: */ -#define DRM_RECT_FP_FMT "%d.%06ux%d.%06u%+d.%06u%+d.%06u" -#define DRM_RECT_FP_ARG(r) \ - drm_rect_width(r) >> 16, ((drm_rect_width(r) & 0xffff) * 15625) >> 10, \ - drm_rect_height(r) >> 16, ((drm_rect_height(r) & 0xffff) * 15625) >> 10, \ - (r)->x1 >> 16, (((r)->x1 & 0xffff) * 15625) >> 10, \ - (r)->y1 >> 16, (((r)->y1 & 0xffff) * 15625) >> 10 /*@}*/ diff --git a/include/drm/drm_modes.h b/include/drm/drm_modes.h index 6dd34280e892d..94ac771fe460b 100644 --- a/include/drm/drm_modes.h +++ b/include/drm/drm_modes.h @@ -197,6 +197,8 @@ enum drm_mode_status { * there's the hardware timings, which are corrected for interlacing, * double-clocking and similar things. They are provided as a convenience, and * can be appropriately computed using drm_mode_set_crtcinfo(). + * + * For printing you can use %DRM_MODE_FMT and DRM_MODE_ARG(). */ struct drm_display_mode { /** @@ -407,6 +409,21 @@ struct drm_display_mode { enum hdmi_picture_aspect picture_aspect_ratio; }; +/** + * DRM_MODE_FMT - printf string for &struct drm_display_mode + */ +#define DRM_MODE_FMT "%d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x" + +/** + * DRM_MODE_ARG - printf arguments for &struct drm_display_mode + * @m: display mode + */ +#define DRM_MODE_ARG(m) \ + (m)->base.id, (m)->name, (m)->vrefresh, (m)->clock, \ + (m)->hdisplay, (m)->hsync_start, (m)->hsync_end, (m)->htotal, \ + (m)->vdisplay, (m)->vsync_start, (m)->vsync_end, (m)->vtotal, \ + (m)->type, (m)->flags + #define obj_to_mode(x) container_of(x, struct drm_display_mode, base) /** diff --git a/include/drm/drm_rect.h b/include/drm/drm_rect.h index 83bb156d4356f..44bc122b9ee04 100644 --- a/include/drm/drm_rect.h +++ b/include/drm/drm_rect.h @@ -42,6 +42,33 @@ struct drm_rect { int x1, y1, x2, y2; }; +/** + * DRM_RECT_FMT - printf string for &struct drm_rect + */ +#define DRM_RECT_FMT "%dx%d%+d%+d" +/** + * DRM_RECT_ARG - printf arguments for &struct drm_rect + * @r: rectangle struct + */ +#define DRM_RECT_ARG(r) drm_rect_width(r), drm_rect_height(r), (r)->x1, (r)->y1 + +/** + * DRM_RECT_FP_FMT - printf string for &struct drm_rect in 16.16 fixed point + */ +#define DRM_RECT_FP_FMT "%d.%06ux%d.%06u%+d.%06u%+d.%06u" +/** + * DRM_RECT_FP_ARG - printf arguments for &struct drm_rect in 16.16 fixed point + * @r: rectangle struct + * + * This is useful for e.g. printing plane source rectangles, which are in 16.16 + * fixed point. + */ +#define DRM_RECT_FP_ARG(r) \ + drm_rect_width(r) >> 16, ((drm_rect_width(r) & 0xffff) * 15625) >> 10, \ + drm_rect_height(r) >> 16, ((drm_rect_height(r) & 0xffff) * 15625) >> 10, \ + (r)->x1 >> 16, (((r)->x1 & 0xffff) * 15625) >> 10, \ + (r)->y1 >> 16, (((r)->y1 & 0xffff) * 15625) >> 10 + /** * drm_rect_adjust_size - adjust the size of the rectangle * @r: rectangle to be adjusted From 46466b0dac3f6bee397c1db4f0ebfedf3e8c9e9c Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Tue, 30 May 2017 16:35:37 -0700 Subject: [PATCH 17/34] drm: Use vsnprintf extension %ph Using the extension saves a bit of code. Miscellanea: o Neaten and simplify dump_dp_payload_table o Removed trailing blank space from output $ size drivers/gpu/drm/drm_dp_mst_topology.o.* drivers/gpu/drm/tinydrm/*.o* text data bss dec hex filename 25848 0 16 25864 6508 drivers/gpu/drm/drm_dp_mst_topology.o.new 26091 0 16 26107 65fb drivers/gpu/drm/drm_dp_mst_topology.o.old 3362 2 0 3364 d24 drivers/gpu/drm/tinydrm/mipi-dbi.o.new 3376 2 0 3378 d32 drivers/gpu/drm/tinydrm/mipi-dbi.o.old Signed-off-by: Joe Perches Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/a78a21b5f34947da65473a0b7326922cda51a3be.1496187315.git.joe@perches.com --- drivers/gpu/drm/drm_dp_mst_topology.c | 51 +++++++++------------------ drivers/gpu/drm/tinydrm/mipi-dbi.c | 7 ++-- 2 files changed, 18 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index 222eb1a8549b0..bfd237c15e76e 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -2836,16 +2836,15 @@ static void drm_dp_mst_dump_mstb(struct seq_file *m, static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, char *buf) { - int ret; int i; - for (i = 0; i < 4; i++) { - ret = drm_dp_dpcd_read(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS + (i * 16), &buf[i * 16], 16); - if (ret != 16) - break; + + for (i = 0; i < 64; i += 16) { + if (drm_dp_dpcd_read(mgr->aux, + DP_PAYLOAD_TABLE_UPDATE_STATUS + i, + &buf[i], 16) != 16) + return false; } - if (i == 4) - return true; - return false; + return true; } static void fetch_monitor_name(struct drm_dp_mst_topology_mgr *mgr, @@ -2909,42 +2908,24 @@ void drm_dp_mst_dump_topology(struct seq_file *m, mutex_lock(&mgr->lock); if (mgr->mst_primary) { u8 buf[64]; - bool bret; int ret; + ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, buf, DP_RECEIVER_CAP_SIZE); - seq_printf(m, "dpcd: "); - for (i = 0; i < DP_RECEIVER_CAP_SIZE; i++) - seq_printf(m, "%02x ", buf[i]); - seq_printf(m, "\n"); + seq_printf(m, "dpcd: %*ph\n", DP_RECEIVER_CAP_SIZE, buf); ret = drm_dp_dpcd_read(mgr->aux, DP_FAUX_CAP, buf, 2); - seq_printf(m, "faux/mst: "); - for (i = 0; i < 2; i++) - seq_printf(m, "%02x ", buf[i]); - seq_printf(m, "\n"); + seq_printf(m, "faux/mst: %*ph\n", 2, buf); ret = drm_dp_dpcd_read(mgr->aux, DP_MSTM_CTRL, buf, 1); - seq_printf(m, "mst ctrl: "); - for (i = 0; i < 1; i++) - seq_printf(m, "%02x ", buf[i]); - seq_printf(m, "\n"); + seq_printf(m, "mst ctrl: %*ph\n", 1, buf); /* dump the standard OUI branch header */ ret = drm_dp_dpcd_read(mgr->aux, DP_BRANCH_OUI, buf, DP_BRANCH_OUI_HEADER_SIZE); - seq_printf(m, "branch oui: "); - for (i = 0; i < 0x3; i++) - seq_printf(m, "%02x", buf[i]); - seq_printf(m, " devid: "); + seq_printf(m, "branch oui: %*phN devid: ", 3, buf); for (i = 0x3; i < 0x8 && buf[i]; i++) seq_printf(m, "%c", buf[i]); - - seq_printf(m, " revision: hw: %x.%x sw: %x.%x", buf[0x9] >> 4, buf[0x9] & 0xf, buf[0xa], buf[0xb]); - seq_printf(m, "\n"); - bret = dump_dp_payload_table(mgr, buf); - if (bret == true) { - seq_printf(m, "payload table: "); - for (i = 0; i < 63; i++) - seq_printf(m, "%02x ", buf[i]); - seq_printf(m, "\n"); - } + seq_printf(m, " revision: hw: %x.%x sw: %x.%x\n", + buf[0x9] >> 4, buf[0x9] & 0xf, buf[0xa], buf[0xb]); + if (dump_dp_payload_table(mgr, buf)) + seq_printf(m, "payload table: %*ph\n", 63, buf); } diff --git a/drivers/gpu/drm/tinydrm/mipi-dbi.c b/drivers/gpu/drm/tinydrm/mipi-dbi.c index f4eb412f36040..c83eeb7a34b00 100644 --- a/drivers/gpu/drm/tinydrm/mipi-dbi.c +++ b/drivers/gpu/drm/tinydrm/mipi-dbi.c @@ -914,7 +914,7 @@ static int mipi_dbi_debugfs_command_show(struct seq_file *m, void *unused) { struct mipi_dbi *mipi = m->private; u8 cmd, val[4]; - size_t len, i; + size_t len; int ret; for (cmd = 0; cmd < 255; cmd++) { @@ -943,10 +943,7 @@ static int mipi_dbi_debugfs_command_show(struct seq_file *m, void *unused) seq_puts(m, "XX\n"); continue; } - - for (i = 0; i < len; i++) - seq_printf(m, "%02x", val[i]); - seq_puts(m, "\n"); + seq_printf(m, "%*phN\n", (int)len, val); } return 0; From 8938d13585bc91e3c5217dc76534759eec2b6e69 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 24 May 2017 16:51:48 +0200 Subject: [PATCH 18/34] drm: better document how to send out the crtc disable event The kernel doc explained what needs to happen, but not how to most easily accomplish that using the functions. Fix that. Cc: Boris Brezillon Reviewed-by: Boris Brezillon Reviewed-by: Neil Armstrong Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-14-daniel.vetter@ffwll.ch --- include/drm/drm_crtc.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index b6e3713bd7a94..629a5fe075b3f 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -214,7 +214,9 @@ struct drm_crtc_state { * atomic commit. In that case the event can be send out any time * after the hardware has stopped scanning out the current * framebuffers. It should contain the timestamp and counter for the - * last vblank before the display pipeline was shut off. + * last vblank before the display pipeline was shut off. The simplest + * way to achieve that is calling drm_crtc_send_vblank_event() + * somewhen after drm_crtc_vblank_off() has been called. * * - For a CRTC which is enabled at the end of the commit (even when it * undergoes an full modeset) the vblank timestamp and counter must From d79373bd4d7e88451043ef7e4f5d1a9139e9c337 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 24 May 2017 16:52:07 +0200 Subject: [PATCH 19/34] drm/sun4i: Drop drm_vblank_cleanup Again seems just cargo-culted ... It's not ordered against any irq/vblank/modeset shutdown. Cc: Maxime Ripard Acked-by: Maxime Ripard Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-33-daniel.vetter@ffwll.ch --- drivers/gpu/drm/sun4i/sun4i_drv.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index 8ddd72cd58736..c26d5888f8e19 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -138,7 +138,6 @@ static int sun4i_drv_bind(struct device *dev) sun4i_framebuffer_free(drm); cleanup_mode_config: drm_mode_config_cleanup(drm); - drm_vblank_cleanup(drm); free_mem_region: of_reserved_mem_device_release(dev); free_drm: @@ -154,7 +153,6 @@ static void sun4i_drv_unbind(struct device *dev) drm_kms_helper_poll_fini(drm); sun4i_framebuffer_free(drm); drm_mode_config_cleanup(drm); - drm_vblank_cleanup(drm); of_reserved_mem_device_release(dev); drm_dev_unref(drm); } From 25b905fe4323f66b65ddce38b7f3fa64d0fe5ae8 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 24 May 2017 16:52:06 +0200 Subject: [PATCH 20/34] drm/stm: Drop drm_vblank_cleanup Again seems just cargo-culted. Cc: Yannick Fertre Cc: Philippe Cornu Acked-by: Philippe Cornu Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-32-daniel.vetter@ffwll.ch --- drivers/gpu/drm/stm/ltdc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index 700cc0800e512..1b9483d4f2a4e 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -1144,8 +1144,6 @@ void ltdc_unload(struct drm_device *ddev) DRM_DEBUG_DRIVER("\n"); - drm_vblank_cleanup(ddev); - if (ldev->panel) drm_panel_detach(ldev->panel); From a360b39b513776a5be64c0f7ee9613de121aa990 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 24 May 2017 16:52:00 +0200 Subject: [PATCH 21/34] drm/meson: Drop drm_vblank_cleanup Again seems just cargo-culted. Cc: Neil Armstrong Acked-by: Neil Armstrong Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-26-daniel.vetter@ffwll.ch --- drivers/gpu/drm/meson/meson_drv.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c index 75382f5f0fcec..2c605a406ad53 100644 --- a/drivers/gpu/drm/meson/meson_drv.c +++ b/drivers/gpu/drm/meson/meson_drv.c @@ -285,7 +285,6 @@ static void meson_drv_unbind(struct device *dev) drm_kms_helper_poll_fini(drm); drm_fbdev_cma_fini(priv->fbdev); drm_mode_config_cleanup(drm); - drm_vblank_cleanup(drm); drm_dev_unref(drm); } From 85769cf7cd7f015ac7c8e45ee8a8f7f485c99481 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 24 May 2017 16:51:58 +0200 Subject: [PATCH 22/34] drm/imx: Drop drm_vblank_cleanup It's only done in the driver load error path, where vblanks don't need to be quiescent anyway. And that's all drm_vblank_cleanup does, since the core will release the vblank allocations on its own already. So drop it. Cc: Philipp Zabel Acked-by: Philipp Zabel Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-24-daniel.vetter@ffwll.ch --- drivers/gpu/drm/imx/imx-drm-core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/imx/imx-drm-core.c b/drivers/gpu/drm/imx/imx-drm-core.c index 50add2f9e250b..95e2181963d9a 100644 --- a/drivers/gpu/drm/imx/imx-drm-core.c +++ b/drivers/gpu/drm/imx/imx-drm-core.c @@ -278,7 +278,7 @@ static int imx_drm_bind(struct device *dev) /* Now try and bind all our sub-components */ ret = component_bind_all(dev, drm); if (ret) - goto err_vblank; + goto err_kms; drm_mode_config_reset(drm); @@ -316,8 +316,6 @@ static int imx_drm_bind(struct device *dev) err_unbind: #endif component_unbind_all(drm->dev, drm); -err_vblank: - drm_vblank_cleanup(drm); err_kms: drm_mode_config_cleanup(drm); err_unref: From e3ec0e1e38a57ced1449b09be23e87da5d91e2b8 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 24 May 2017 16:51:52 +0200 Subject: [PATCH 23/34] drm/atmel: Drop drm_vblank_cleanup Again almost correct, but since interrupts are shut down after vblank still a race. Proper cleanup would call drm_atomic_helper_shutdown to make sure this really is safe. v2: Remove misplace malidp hunk (Liviu). Cc: Boris Brezillon Acked-by: Boris Brezillon Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-18-daniel.vetter@ffwll.ch --- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c index f4a3065f7f517..288dd4b701262 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c @@ -678,7 +678,6 @@ static void atmel_hlcdc_dc_unload(struct drm_device *dev) flush_workqueue(dc->wq); drm_kms_helper_poll_fini(dev); drm_mode_config_cleanup(dev); - drm_vblank_cleanup(dev); pm_runtime_get_sync(dev->dev); drm_irq_uninstall(dev); From fb51cbfd4b2ee030b9021b91bca96fcf2cbba875 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 24 May 2017 16:51:50 +0200 Subject: [PATCH 24/34] drm/arcgpu: Drop drm_vblank_cleanup CRTC don't seem to get shut down in a controlled fashion, but no one bothers to stop interrupts either so this races no matter what. Might as well remove it. A call to drm_atomic_helper_shutdown would be pretty sweet somewhere (and maybe getting rid of the load/unload callbacks while at it). Cc: Alexey Brodkin Reviewed-by: Alexey Brodkin Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-16-daniel.vetter@ffwll.ch Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-14-daniel.vetter@ffwll.ch --- drivers/gpu/drm/arc/arcpgu_drv.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/arc/arcpgu_drv.c b/drivers/gpu/drm/arc/arcpgu_drv.c index 1926b200e4cb3..3e43a5d4fb09e 100644 --- a/drivers/gpu/drm/arc/arcpgu_drv.c +++ b/drivers/gpu/drm/arc/arcpgu_drv.c @@ -155,7 +155,6 @@ static int arcpgu_unload(struct drm_device *drm) arcpgu->fbdev = NULL; } drm_kms_helper_poll_fini(drm); - drm_vblank_cleanup(drm); drm_mode_config_cleanup(drm); return 0; From 91110a4b64fe0bdbac084b79dbd5f10c458327e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Thu, 18 May 2017 22:38:36 +0300 Subject: [PATCH 25/34] drm/atomic: Consitfy mode parameter to drm_atomic_set_mode_for_crtc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_atomic_set_mode_for_crtc() doesn't modify the passed mode, so let's make it const. Signed-off-by: Ville Syrjälä Link: http://patchwork.freedesktop.org/patch/msgid/20170518193837.393-1-ville.syrjala@linux.intel.com Reviewed-by: Daniel Vetter --- drivers/gpu/drm/drm_atomic.c | 2 +- include/drm/drm_atomic.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index e1637011e18ab..77dcef00998c6 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -328,7 +328,7 @@ static s32 __user *get_out_fence_for_crtc(struct drm_atomic_state *state, * Zero on success, error code on failure. Cannot return -EDEADLK. */ int drm_atomic_set_mode_for_crtc(struct drm_crtc_state *state, - struct drm_display_mode *mode) + const struct drm_display_mode *mode) { struct drm_mode_modeinfo umode; diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h index 8645dcdef0317..0196f264a418a 100644 --- a/include/drm/drm_atomic.h +++ b/include/drm/drm_atomic.h @@ -520,7 +520,7 @@ __drm_atomic_get_current_plane_state(struct drm_atomic_state *state, int __must_check drm_atomic_set_mode_for_crtc(struct drm_crtc_state *state, - struct drm_display_mode *mode); + const struct drm_display_mode *mode); int __must_check drm_atomic_set_mode_prop_for_crtc(struct drm_crtc_state *state, struct drm_property_blob *blob); From 66d6dd45eee2893f249a13406e112a35cc86c987 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 24 May 2017 17:49:58 +0200 Subject: [PATCH 26/34] drm/pl111: fix warnings without CONFIG_ARM_AMBA The driver is written in a way to enable compile-testing without CONFIG_ARM_AMBA, but it just causes needless warnings: drivers/gpu/drm/pl111/pl111_drv.c:149:26: error: 'pl111_drm_driver' defined but not used [-Werror=unused-variable] drivers/gpu/drm/pl111/pl111_drv.c:81:12: error: 'pl111_modeset_init' defined but not used [-Werror=unused-function] This unhides the probe/remove functions again and just leaves the driver object as unused when CONFIG_ARM_AMBA is disabled, with a __maybe_unused annotation to shut up the warning. Fixes: bed41005e617 ("drm/pl111: Initial drm/kms driver for pl111") Signed-off-by: Arnd Bergmann Reviewed-by: Eric Anholt Link: http://patchwork.freedesktop.org/patch/msgid/20170524155020.1777369-1-arnd@arndb.de --- drivers/gpu/drm/pl111/pl111_drv.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c index e96efad37d274..ac8771be70b0d 100644 --- a/drivers/gpu/drm/pl111/pl111_drv.c +++ b/drivers/gpu/drm/pl111/pl111_drv.c @@ -179,7 +179,6 @@ static struct drm_driver pl111_drm_driver = { #endif }; -#ifdef CONFIG_ARM_AMBA static int pl111_amba_probe(struct amba_device *amba_dev, const struct amba_id *id) { @@ -252,7 +251,7 @@ static struct amba_id pl111_id_table[] = { {0, 0}, }; -static struct amba_driver pl111_amba_driver = { +static struct amba_driver pl111_amba_driver __maybe_unused = { .drv = { .name = "drm-clcd-pl111", }, @@ -261,8 +260,9 @@ static struct amba_driver pl111_amba_driver = { .id_table = pl111_id_table, }; +#ifdef CONFIG_ARM_AMBA module_amba_driver(pl111_amba_driver); -#endif /* CONFIG_ARM_AMBA */ +#endif MODULE_DESCRIPTION(DRIVER_DESC); MODULE_AUTHOR("ARM Ltd."); From 9a8d5e4a53dc0f14334d7b19b9107639fcfefd35 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 23 May 2017 16:36:27 +0200 Subject: [PATCH 27/34] drm/vc4: Fix comment in vc4_drv.h Fixes a copy&paste error. Signed-off-by: Boris Brezillon Reviewed-by: Eric Anholt Link: http://patchwork.freedesktop.org/patch/msgid/1495550187-525-1-git-send-email-boris.brezillon@free-electrons.com --- drivers/gpu/drm/vc4/vc4_drv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index a97556f7ccba7..a5bf2e5e0b57f 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -532,7 +532,7 @@ int vc4_queue_seqno_cb(struct drm_device *dev, extern struct platform_driver vc4_hdmi_driver; int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused); -/* vc4_hdmi.c */ +/* vc4_vec.c */ extern struct platform_driver vc4_vec_driver; int vc4_vec_debugfs_regs(struct seq_file *m, void *unused); From 3ed4351a83ca05d3cd886ade6900be1067aa7903 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 31 May 2017 11:21:46 +0200 Subject: [PATCH 28/34] drm: Extract drm_vblank.[hc] drm_irq.c contains both the irq helper library (optional) and the vblank support (optional, but part of the modeset uapi, and doesn't require the use of the irq helpers at all. Split this up for more clarity of the scope of the individual bits. v2: Move misplaced hunks to this patch (Stefan). Cc: Stefan Agner Reviewed-by: Stefan Agner Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170531092146.12528-1-daniel.vetter@ffwll.ch --- Documentation/gpu/drm-internals.rst | 9 + Documentation/gpu/drm-kms.rst | 4 +- drivers/gpu/drm/Makefile | 2 +- drivers/gpu/drm/drm_internal.h | 3 +- drivers/gpu/drm/drm_irq.c | 1623 +------------------------- drivers/gpu/drm/drm_vblank.c | 1645 +++++++++++++++++++++++++++ include/drm/drmP.h | 5 +- include/drm/drm_file.h | 1 + include/drm/drm_irq.h | 158 +-- include/drm/drm_prime.h | 2 + include/drm/drm_vblank.h | 181 +++ 11 files changed, 1869 insertions(+), 1764 deletions(-) create mode 100644 drivers/gpu/drm/drm_vblank.c create mode 100644 include/drm/drm_vblank.h diff --git a/Documentation/gpu/drm-internals.rst b/Documentation/gpu/drm-internals.rst index babfb6143bd97..9067cd9586bd3 100644 --- a/Documentation/gpu/drm-internals.rst +++ b/Documentation/gpu/drm-internals.rst @@ -204,6 +204,15 @@ drm_device ` irq_enabled field to 1 upon registration of the IRQs, and clear it to 0 after unregistering the IRQs. +IRQ Helper Library +~~~~~~~~~~~~~~~~~~ + +.. kernel-doc:: drivers/gpu/drm/drm_irq.c + :doc: irq helpers + +.. kernel-doc:: drivers/gpu/drm/drm_irq.c + :export: + Memory Manager Initialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst index bfecd21a8cdf4..2d77c95801648 100644 --- a/Documentation/gpu/drm-kms.rst +++ b/Documentation/gpu/drm-kms.rst @@ -612,8 +612,8 @@ operation handler. Vertical Blanking and Interrupt Handling Functions Reference ------------------------------------------------------------ -.. kernel-doc:: include/drm/drm_irq.h +.. kernel-doc:: include/drm/drm_vblank.h :internal: -.. kernel-doc:: drivers/gpu/drm/drm_irq.c +.. kernel-doc:: drivers/gpu/drm/drm_vblank.c :export: diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index c156fecfb3624..acc88942c2e51 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -16,7 +16,7 @@ drm-y := drm_auth.o drm_bufs.o drm_cache.o \ drm_framebuffer.o drm_connector.o drm_blend.o \ drm_encoder.o drm_mode_object.o drm_property.o \ drm_plane.o drm_color_mgmt.o drm_print.o \ - drm_dumb_buffers.o drm_mode_config.o + drm_dumb_buffers.o drm_mode_config.o drm_vblank.o drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o drm-$(CONFIG_DRM_VM) += drm_vm.o diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h index 3d8e8f878924d..ba3f5fb219592 100644 --- a/drivers/gpu/drm/drm_internal.h +++ b/drivers/gpu/drm/drm_internal.h @@ -53,8 +53,9 @@ int drm_name_info(struct seq_file *m, void *data); int drm_clients_info(struct seq_file *m, void* data); int drm_gem_name_info(struct seq_file *m, void *data); -/* drm_irq.c */ +/* drm_vblank.c */ extern unsigned int drm_timestamp_monotonic; +void drm_vblank_disable_and_save(struct drm_device *dev, unsigned int pipe); /* IOCTLS */ int drm_wait_vblank(struct drm_device *dev, void *data, diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index c7debaad67f87..28d736c3fcb4f 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -3,6 +3,25 @@ * * \author Rickard E. (Rik) Faith * \author Gareth Hughes + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. */ /* @@ -32,429 +51,15 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include #include -#include "drm_trace.h" -#include "drm_internal.h" #include /* For task queue support */ -#include #include #include -/* Retry timestamp calculation up to 3 times to satisfy - * drm_timestamp_precision before giving up. - */ -#define DRM_TIMESTAMP_MAXRETRIES 3 - -/* Threshold in nanoseconds for detection of redundant - * vblank irq in drm_handle_vblank(). 1 msec should be ok. - */ -#define DRM_REDUNDANT_VBLIRQ_THRESH_NS 1000000 - -static bool -drm_get_last_vbltimestamp(struct drm_device *dev, unsigned int pipe, - struct timeval *tvblank, bool in_vblank_irq); - -static unsigned int drm_timestamp_precision = 20; /* Default to 20 usecs. */ - -/* - * Default to use monotonic timestamps for wait-for-vblank and page-flip - * complete events. - */ -unsigned int drm_timestamp_monotonic = 1; - -static int drm_vblank_offdelay = 5000; /* Default to 5000 msecs. */ - -module_param_named(vblankoffdelay, drm_vblank_offdelay, int, 0600); -module_param_named(timestamp_precision_usec, drm_timestamp_precision, int, 0600); -module_param_named(timestamp_monotonic, drm_timestamp_monotonic, int, 0600); -MODULE_PARM_DESC(vblankoffdelay, "Delay until vblank irq auto-disable [msecs] (0: never disable, <0: disable immediately)"); -MODULE_PARM_DESC(timestamp_precision_usec, "Max. error on timestamps [usecs]"); -MODULE_PARM_DESC(timestamp_monotonic, "Use monotonic timestamps"); - -static void store_vblank(struct drm_device *dev, unsigned int pipe, - u32 vblank_count_inc, - struct timeval *t_vblank, u32 last) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - - assert_spin_locked(&dev->vblank_time_lock); - - vblank->last = last; - - write_seqlock(&vblank->seqlock); - vblank->time = *t_vblank; - vblank->count += vblank_count_inc; - write_sequnlock(&vblank->seqlock); -} - -/* - * "No hw counter" fallback implementation of .get_vblank_counter() hook, - * if there is no useable hardware frame counter available. - */ -static u32 drm_vblank_no_hw_counter(struct drm_device *dev, unsigned int pipe) -{ - WARN_ON_ONCE(dev->max_vblank_count != 0); - return 0; -} - -static u32 __get_vblank_counter(struct drm_device *dev, unsigned int pipe) -{ - if (drm_core_check_feature(dev, DRIVER_MODESET)) { - struct drm_crtc *crtc = drm_crtc_from_index(dev, pipe); - - if (crtc->funcs->get_vblank_counter) - return crtc->funcs->get_vblank_counter(crtc); - } - - if (dev->driver->get_vblank_counter) - return dev->driver->get_vblank_counter(dev, pipe); - - return drm_vblank_no_hw_counter(dev, pipe); -} - -/* - * Reset the stored timestamp for the current vblank count to correspond - * to the last vblank occurred. - * - * Only to be called from drm_crtc_vblank_on(). - * - * Note: caller must hold &drm_device.vbl_lock since this reads & writes - * device vblank fields. - */ -static void drm_reset_vblank_timestamp(struct drm_device *dev, unsigned int pipe) -{ - u32 cur_vblank; - bool rc; - struct timeval t_vblank; - int count = DRM_TIMESTAMP_MAXRETRIES; - - spin_lock(&dev->vblank_time_lock); - - /* - * sample the current counter to avoid random jumps - * when drm_vblank_enable() applies the diff - */ - do { - cur_vblank = __get_vblank_counter(dev, pipe); - rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, false); - } while (cur_vblank != __get_vblank_counter(dev, pipe) && --count > 0); - - /* - * Only reinitialize corresponding vblank timestamp if high-precision query - * available and didn't fail. Otherwise reinitialize delayed at next vblank - * interrupt and assign 0 for now, to mark the vblanktimestamp as invalid. - */ - if (!rc) - t_vblank = (struct timeval) {0, 0}; - - /* - * +1 to make sure user will never see the same - * vblank counter value before and after a modeset - */ - store_vblank(dev, pipe, 1, &t_vblank, cur_vblank); - - spin_unlock(&dev->vblank_time_lock); -} - -/* - * Call back into the driver to update the appropriate vblank counter - * (specified by @pipe). Deal with wraparound, if it occurred, and - * update the last read value so we can deal with wraparound on the next - * call if necessary. - * - * Only necessary when going from off->on, to account for frames we - * didn't get an interrupt for. - * - * Note: caller must hold &drm_device.vbl_lock since this reads & writes - * device vblank fields. - */ -static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe, - bool in_vblank_irq) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - u32 cur_vblank, diff; - bool rc; - struct timeval t_vblank; - int count = DRM_TIMESTAMP_MAXRETRIES; - int framedur_ns = vblank->framedur_ns; - - /* - * Interrupts were disabled prior to this call, so deal with counter - * wrap if needed. - * NOTE! It's possible we lost a full dev->max_vblank_count + 1 events - * here if the register is small or we had vblank interrupts off for - * a long time. - * - * We repeat the hardware vblank counter & timestamp query until - * we get consistent results. This to prevent races between gpu - * updating its hardware counter while we are retrieving the - * corresponding vblank timestamp. - */ - do { - cur_vblank = __get_vblank_counter(dev, pipe); - rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, in_vblank_irq); - } while (cur_vblank != __get_vblank_counter(dev, pipe) && --count > 0); - - if (dev->max_vblank_count != 0) { - /* trust the hw counter when it's around */ - diff = (cur_vblank - vblank->last) & dev->max_vblank_count; - } else if (rc && framedur_ns) { - const struct timeval *t_old; - u64 diff_ns; - - t_old = &vblank->time; - diff_ns = timeval_to_ns(&t_vblank) - timeval_to_ns(t_old); - - /* - * Figure out how many vblanks we've missed based - * on the difference in the timestamps and the - * frame/field duration. - */ - diff = DIV_ROUND_CLOSEST_ULL(diff_ns, framedur_ns); - - if (diff == 0 && in_vblank_irq) - DRM_DEBUG_VBL("crtc %u: Redundant vblirq ignored." - " diff_ns = %lld, framedur_ns = %d)\n", - pipe, (long long) diff_ns, framedur_ns); - } else { - /* some kind of default for drivers w/o accurate vbl timestamping */ - diff = in_vblank_irq ? 1 : 0; - } - - /* - * Within a drm_vblank_pre_modeset - drm_vblank_post_modeset - * interval? If so then vblank irqs keep running and it will likely - * happen that the hardware vblank counter is not trustworthy as it - * might reset at some point in that interval and vblank timestamps - * are not trustworthy either in that interval. Iow. this can result - * in a bogus diff >> 1 which must be avoided as it would cause - * random large forward jumps of the software vblank counter. - */ - if (diff > 1 && (vblank->inmodeset & 0x2)) { - DRM_DEBUG_VBL("clamping vblank bump to 1 on crtc %u: diffr=%u" - " due to pre-modeset.\n", pipe, diff); - diff = 1; - } - - DRM_DEBUG_VBL("updating vblank count on crtc %u:" - " current=%u, diff=%u, hw=%u hw_last=%u\n", - pipe, vblank->count, diff, cur_vblank, vblank->last); - - if (diff == 0) { - WARN_ON_ONCE(cur_vblank != vblank->last); - return; - } - - /* - * Only reinitialize corresponding vblank timestamp if high-precision query - * available and didn't fail, or we were called from the vblank interrupt. - * Otherwise reinitialize delayed at next vblank interrupt and assign 0 - * for now, to mark the vblanktimestamp as invalid. - */ - if (!rc && in_vblank_irq) - t_vblank = (struct timeval) {0, 0}; - - store_vblank(dev, pipe, diff, &t_vblank, cur_vblank); -} - -static u32 drm_vblank_count(struct drm_device *dev, unsigned int pipe) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - - if (WARN_ON(pipe >= dev->num_crtcs)) - return 0; - - return vblank->count; -} - -/** - * drm_accurate_vblank_count - retrieve the master vblank counter - * @crtc: which counter to retrieve - * - * This function is similar to @drm_crtc_vblank_count but this - * function interpolates to handle a race with vblank irq's. - * - * This is mostly useful for hardware that can obtain the scanout - * position, but doesn't have a frame counter. - */ -u32 drm_accurate_vblank_count(struct drm_crtc *crtc) -{ - struct drm_device *dev = crtc->dev; - unsigned int pipe = drm_crtc_index(crtc); - u32 vblank; - unsigned long flags; - - WARN(!dev->driver->get_vblank_timestamp, - "This function requires support for accurate vblank timestamps."); - - spin_lock_irqsave(&dev->vblank_time_lock, flags); - - drm_update_vblank_count(dev, pipe, false); - vblank = drm_vblank_count(dev, pipe); - - spin_unlock_irqrestore(&dev->vblank_time_lock, flags); - - return vblank; -} -EXPORT_SYMBOL(drm_accurate_vblank_count); - -static void __disable_vblank(struct drm_device *dev, unsigned int pipe) -{ - if (drm_core_check_feature(dev, DRIVER_MODESET)) { - struct drm_crtc *crtc = drm_crtc_from_index(dev, pipe); - - if (crtc->funcs->disable_vblank) { - crtc->funcs->disable_vblank(crtc); - return; - } - } - - dev->driver->disable_vblank(dev, pipe); -} - -/* - * Disable vblank irq's on crtc, make sure that last vblank count - * of hardware and corresponding consistent software vblank counter - * are preserved, even if there are any spurious vblank irq's after - * disable. - */ -static void vblank_disable_and_save(struct drm_device *dev, unsigned int pipe) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - unsigned long irqflags; - - assert_spin_locked(&dev->vbl_lock); - - /* Prevent vblank irq processing while disabling vblank irqs, - * so no updates of timestamps or count can happen after we've - * disabled. Needed to prevent races in case of delayed irq's. - */ - spin_lock_irqsave(&dev->vblank_time_lock, irqflags); - - /* - * Only disable vblank interrupts if they're enabled. This avoids - * calling the ->disable_vblank() operation in atomic context with the - * hardware potentially runtime suspended. - */ - if (vblank->enabled) { - __disable_vblank(dev, pipe); - vblank->enabled = false; - } - - /* - * Always update the count and timestamp to maintain the - * appearance that the counter has been ticking all along until - * this time. This makes the count account for the entire time - * between drm_crtc_vblank_on() and drm_crtc_vblank_off(). - */ - drm_update_vblank_count(dev, pipe, false); - - spin_unlock_irqrestore(&dev->vblank_time_lock, irqflags); -} - -static void vblank_disable_fn(unsigned long arg) -{ - struct drm_vblank_crtc *vblank = (void *)arg; - struct drm_device *dev = vblank->dev; - unsigned int pipe = vblank->pipe; - unsigned long irqflags; - - spin_lock_irqsave(&dev->vbl_lock, irqflags); - if (atomic_read(&vblank->refcount) == 0 && vblank->enabled) { - DRM_DEBUG("disabling vblank on crtc %u\n", pipe); - vblank_disable_and_save(dev, pipe); - } - spin_unlock_irqrestore(&dev->vbl_lock, irqflags); -} - -/** - * drm_vblank_cleanup - cleanup vblank support - * @dev: DRM device - * - * This function cleans up any resources allocated in drm_vblank_init. - */ -void drm_vblank_cleanup(struct drm_device *dev) -{ - unsigned int pipe; - - /* Bail if the driver didn't call drm_vblank_init() */ - if (dev->num_crtcs == 0) - return; - - for (pipe = 0; pipe < dev->num_crtcs; pipe++) { - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - - WARN_ON(READ_ONCE(vblank->enabled) && - drm_core_check_feature(dev, DRIVER_MODESET)); - - del_timer_sync(&vblank->disable_timer); - } - - kfree(dev->vblank); - - dev->num_crtcs = 0; -} -EXPORT_SYMBOL(drm_vblank_cleanup); - -/** - * drm_vblank_init - initialize vblank support - * @dev: DRM device - * @num_crtcs: number of CRTCs supported by @dev - * - * This function initializes vblank support for @num_crtcs display pipelines. - * - * Returns: - * Zero on success or a negative error code on failure. - */ -int drm_vblank_init(struct drm_device *dev, unsigned int num_crtcs) -{ - int ret = -ENOMEM; - unsigned int i; - - spin_lock_init(&dev->vbl_lock); - spin_lock_init(&dev->vblank_time_lock); - - dev->num_crtcs = num_crtcs; - - dev->vblank = kcalloc(num_crtcs, sizeof(*dev->vblank), GFP_KERNEL); - if (!dev->vblank) - goto err; - - for (i = 0; i < num_crtcs; i++) { - struct drm_vblank_crtc *vblank = &dev->vblank[i]; - - vblank->dev = dev; - vblank->pipe = i; - init_waitqueue_head(&vblank->queue); - setup_timer(&vblank->disable_timer, vblank_disable_fn, - (unsigned long)vblank); - seqlock_init(&vblank->seqlock); - } - - DRM_INFO("Supports vblank timestamp caching Rev 2 (21.10.2013).\n"); - - /* Driver specific high-precision vblank timestamping supported? */ - if (dev->driver->get_vblank_timestamp) - DRM_INFO("Driver supports precise vblank timestamp query.\n"); - else - DRM_INFO("No driver support for vblank timestamp query.\n"); - - /* Must have precise timestamping for reliable vblank instant disable */ - if (dev->vblank_disable_immediate && !dev->driver->get_vblank_timestamp) { - dev->vblank_disable_immediate = false; - DRM_INFO("Setting vblank_disable_immediate to false because " - "get_vblank_timestamp == NULL\n"); - } - - return 0; - -err: - dev->num_crtcs = 0; - return ret; -} -EXPORT_SYMBOL(drm_vblank_init); +#include "drm_internal.h" /** * drm_irq_install - install IRQ handler @@ -571,7 +176,7 @@ int drm_irq_uninstall(struct drm_device *dev) WARN_ON(drm_core_check_feature(dev, DRIVER_MODESET)); - vblank_disable_and_save(dev, i); + drm_vblank_disable_and_save(dev, i); wake_up(&vblank->queue); } spin_unlock_irqrestore(&dev->vbl_lock, irqflags); @@ -634,1187 +239,3 @@ int drm_legacy_irq_control(struct drm_device *dev, void *data, return -EINVAL; } } - -/** - * drm_calc_timestamping_constants - calculate vblank timestamp constants - * @crtc: drm_crtc whose timestamp constants should be updated. - * @mode: display mode containing the scanout timings - * - * Calculate and store various constants which are later - * needed by vblank and swap-completion timestamping, e.g, - * by drm_calc_vbltimestamp_from_scanoutpos(). They are - * derived from CRTC's true scanout timing, so they take - * things like panel scaling or other adjustments into account. - */ -void drm_calc_timestamping_constants(struct drm_crtc *crtc, - const struct drm_display_mode *mode) -{ - struct drm_device *dev = crtc->dev; - unsigned int pipe = drm_crtc_index(crtc); - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - int linedur_ns = 0, framedur_ns = 0; - int dotclock = mode->crtc_clock; - - if (!dev->num_crtcs) - return; - - if (WARN_ON(pipe >= dev->num_crtcs)) - return; - - /* Valid dotclock? */ - if (dotclock > 0) { - int frame_size = mode->crtc_htotal * mode->crtc_vtotal; - - /* - * Convert scanline length in pixels and video - * dot clock to line duration and frame duration - * in nanoseconds: - */ - linedur_ns = div_u64((u64) mode->crtc_htotal * 1000000, dotclock); - framedur_ns = div_u64((u64) frame_size * 1000000, dotclock); - - /* - * Fields of interlaced scanout modes are only half a frame duration. - */ - if (mode->flags & DRM_MODE_FLAG_INTERLACE) - framedur_ns /= 2; - } else - DRM_ERROR("crtc %u: Can't calculate constants, dotclock = 0!\n", - crtc->base.id); - - vblank->linedur_ns = linedur_ns; - vblank->framedur_ns = framedur_ns; - vblank->hwmode = *mode; - - DRM_DEBUG("crtc %u: hwmode: htotal %d, vtotal %d, vdisplay %d\n", - crtc->base.id, mode->crtc_htotal, - mode->crtc_vtotal, mode->crtc_vdisplay); - DRM_DEBUG("crtc %u: clock %d kHz framedur %d linedur %d\n", - crtc->base.id, dotclock, framedur_ns, linedur_ns); -} -EXPORT_SYMBOL(drm_calc_timestamping_constants); - -/** - * drm_calc_vbltimestamp_from_scanoutpos - precise vblank timestamp helper - * @dev: DRM device - * @pipe: index of CRTC whose vblank timestamp to retrieve - * @max_error: Desired maximum allowable error in timestamps (nanosecs) - * On return contains true maximum error of timestamp - * @vblank_time: Pointer to struct timeval which should receive the timestamp - * @in_vblank_irq: - * True when called from drm_crtc_handle_vblank(). Some drivers - * need to apply some workarounds for gpu-specific vblank irq quirks - * if flag is set. - * - * Implements calculation of exact vblank timestamps from given drm_display_mode - * timings and current video scanout position of a CRTC. This can be called from - * within get_vblank_timestamp() implementation of a kms driver to implement the - * actual timestamping. - * - * Should return timestamps conforming to the OML_sync_control OpenML - * extension specification. The timestamp corresponds to the end of - * the vblank interval, aka start of scanout of topmost-leftmost display - * pixel in the following video frame. - * - * Requires support for optional dev->driver->get_scanout_position() - * in kms driver, plus a bit of setup code to provide a drm_display_mode - * that corresponds to the true scanout timing. - * - * The current implementation only handles standard video modes. It - * returns as no operation if a doublescan or interlaced video mode is - * active. Higher level code is expected to handle this. - * - * This function can be used to implement the &drm_driver.get_vblank_timestamp - * directly, if the driver implements the &drm_driver.get_scanout_position hook. - * - * Note that atomic drivers must call drm_calc_timestamping_constants() before - * enabling a CRTC. The atomic helpers already take care of that in - * drm_atomic_helper_update_legacy_modeset_state(). - * - * Returns: - * - * Returns true on success, and false on failure, i.e. when no accurate - * timestamp could be acquired. - */ -bool drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, - unsigned int pipe, - int *max_error, - struct timeval *vblank_time, - bool in_vblank_irq) -{ - struct timeval tv_etime; - ktime_t stime, etime; - bool vbl_status; - struct drm_crtc *crtc; - const struct drm_display_mode *mode; - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - int vpos, hpos, i; - int delta_ns, duration_ns; - - if (!drm_core_check_feature(dev, DRIVER_MODESET)) - return false; - - crtc = drm_crtc_from_index(dev, pipe); - - if (pipe >= dev->num_crtcs || !crtc) { - DRM_ERROR("Invalid crtc %u\n", pipe); - return false; - } - - /* Scanout position query not supported? Should not happen. */ - if (!dev->driver->get_scanout_position) { - DRM_ERROR("Called from driver w/o get_scanout_position()!?\n"); - return false; - } - - if (drm_drv_uses_atomic_modeset(dev)) - mode = &vblank->hwmode; - else - mode = &crtc->hwmode; - - /* If mode timing undefined, just return as no-op: - * Happens during initial modesetting of a crtc. - */ - if (mode->crtc_clock == 0) { - DRM_DEBUG("crtc %u: Noop due to uninitialized mode.\n", pipe); - WARN_ON_ONCE(drm_drv_uses_atomic_modeset(dev)); - - return false; - } - - /* Get current scanout position with system timestamp. - * Repeat query up to DRM_TIMESTAMP_MAXRETRIES times - * if single query takes longer than max_error nanoseconds. - * - * This guarantees a tight bound on maximum error if - * code gets preempted or delayed for some reason. - */ - for (i = 0; i < DRM_TIMESTAMP_MAXRETRIES; i++) { - /* - * Get vertical and horizontal scanout position vpos, hpos, - * and bounding timestamps stime, etime, pre/post query. - */ - vbl_status = dev->driver->get_scanout_position(dev, pipe, - in_vblank_irq, - &vpos, &hpos, - &stime, &etime, - mode); - - /* Return as no-op if scanout query unsupported or failed. */ - if (!vbl_status) { - DRM_DEBUG("crtc %u : scanoutpos query failed.\n", - pipe); - return false; - } - - /* Compute uncertainty in timestamp of scanout position query. */ - duration_ns = ktime_to_ns(etime) - ktime_to_ns(stime); - - /* Accept result with < max_error nsecs timing uncertainty. */ - if (duration_ns <= *max_error) - break; - } - - /* Noisy system timing? */ - if (i == DRM_TIMESTAMP_MAXRETRIES) { - DRM_DEBUG("crtc %u: Noisy timestamp %d us > %d us [%d reps].\n", - pipe, duration_ns/1000, *max_error/1000, i); - } - - /* Return upper bound of timestamp precision error. */ - *max_error = duration_ns; - - /* Convert scanout position into elapsed time at raw_time query - * since start of scanout at first display scanline. delta_ns - * can be negative if start of scanout hasn't happened yet. - */ - delta_ns = div_s64(1000000LL * (vpos * mode->crtc_htotal + hpos), - mode->crtc_clock); - - if (!drm_timestamp_monotonic) - etime = ktime_mono_to_real(etime); - - /* save this only for debugging purposes */ - tv_etime = ktime_to_timeval(etime); - /* Subtract time delta from raw timestamp to get final - * vblank_time timestamp for end of vblank. - */ - etime = ktime_sub_ns(etime, delta_ns); - *vblank_time = ktime_to_timeval(etime); - - DRM_DEBUG_VBL("crtc %u : v p(%d,%d)@ %ld.%ld -> %ld.%ld [e %d us, %d rep]\n", - pipe, hpos, vpos, - (long)tv_etime.tv_sec, (long)tv_etime.tv_usec, - (long)vblank_time->tv_sec, (long)vblank_time->tv_usec, - duration_ns/1000, i); - - return true; -} -EXPORT_SYMBOL(drm_calc_vbltimestamp_from_scanoutpos); - -static struct timeval get_drm_timestamp(void) -{ - ktime_t now; - - now = drm_timestamp_monotonic ? ktime_get() : ktime_get_real(); - return ktime_to_timeval(now); -} - -/** - * drm_get_last_vbltimestamp - retrieve raw timestamp for the most recent - * vblank interval - * @dev: DRM device - * @pipe: index of CRTC whose vblank timestamp to retrieve - * @tvblank: Pointer to target struct timeval which should receive the timestamp - * @in_vblank_irq: - * True when called from drm_crtc_handle_vblank(). Some drivers - * need to apply some workarounds for gpu-specific vblank irq quirks - * if flag is set. - * - * Fetches the system timestamp corresponding to the time of the most recent - * vblank interval on specified CRTC. May call into kms-driver to - * compute the timestamp with a high-precision GPU specific method. - * - * Returns zero if timestamp originates from uncorrected do_gettimeofday() - * call, i.e., it isn't very precisely locked to the true vblank. - * - * Returns: - * True if timestamp is considered to be very precise, false otherwise. - */ -static bool -drm_get_last_vbltimestamp(struct drm_device *dev, unsigned int pipe, - struct timeval *tvblank, bool in_vblank_irq) -{ - bool ret = false; - - /* Define requested maximum error on timestamps (nanoseconds). */ - int max_error = (int) drm_timestamp_precision * 1000; - - /* Query driver if possible and precision timestamping enabled. */ - if (dev->driver->get_vblank_timestamp && (max_error > 0)) - ret = dev->driver->get_vblank_timestamp(dev, pipe, &max_error, - tvblank, in_vblank_irq); - - /* GPU high precision timestamp query unsupported or failed. - * Return current monotonic/gettimeofday timestamp as best estimate. - */ - if (!ret) - *tvblank = get_drm_timestamp(); - - return ret; -} - -/** - * drm_crtc_vblank_count - retrieve "cooked" vblank counter value - * @crtc: which counter to retrieve - * - * Fetches the "cooked" vblank count value that represents the number of - * vblank events since the system was booted, including lost events due to - * modesetting activity. - * - * Returns: - * The software vblank counter. - */ -u32 drm_crtc_vblank_count(struct drm_crtc *crtc) -{ - return drm_vblank_count(crtc->dev, drm_crtc_index(crtc)); -} -EXPORT_SYMBOL(drm_crtc_vblank_count); - -/** - * drm_vblank_count_and_time - retrieve "cooked" vblank counter value and the - * system timestamp corresponding to that vblank counter value. - * @dev: DRM device - * @pipe: index of CRTC whose counter to retrieve - * @vblanktime: Pointer to struct timeval to receive the vblank timestamp. - * - * Fetches the "cooked" vblank count value that represents the number of - * vblank events since the system was booted, including lost events due to - * modesetting activity. Returns corresponding system timestamp of the time - * of the vblank interval that corresponds to the current vblank counter value. - * - * This is the legacy version of drm_crtc_vblank_count_and_time(). - */ -static u32 drm_vblank_count_and_time(struct drm_device *dev, unsigned int pipe, - struct timeval *vblanktime) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - u32 vblank_count; - unsigned int seq; - - if (WARN_ON(pipe >= dev->num_crtcs)) { - *vblanktime = (struct timeval) { 0 }; - return 0; - } - - do { - seq = read_seqbegin(&vblank->seqlock); - vblank_count = vblank->count; - *vblanktime = vblank->time; - } while (read_seqretry(&vblank->seqlock, seq)); - - return vblank_count; -} - -/** - * drm_crtc_vblank_count_and_time - retrieve "cooked" vblank counter value - * and the system timestamp corresponding to that vblank counter value - * @crtc: which counter to retrieve - * @vblanktime: Pointer to struct timeval to receive the vblank timestamp. - * - * Fetches the "cooked" vblank count value that represents the number of - * vblank events since the system was booted, including lost events due to - * modesetting activity. Returns corresponding system timestamp of the time - * of the vblank interval that corresponds to the current vblank counter value. - */ -u32 drm_crtc_vblank_count_and_time(struct drm_crtc *crtc, - struct timeval *vblanktime) -{ - return drm_vblank_count_and_time(crtc->dev, drm_crtc_index(crtc), - vblanktime); -} -EXPORT_SYMBOL(drm_crtc_vblank_count_and_time); - -static void send_vblank_event(struct drm_device *dev, - struct drm_pending_vblank_event *e, - unsigned long seq, struct timeval *now) -{ - e->event.sequence = seq; - e->event.tv_sec = now->tv_sec; - e->event.tv_usec = now->tv_usec; - - trace_drm_vblank_event_delivered(e->base.file_priv, e->pipe, - e->event.sequence); - - drm_send_event_locked(dev, &e->base); -} - -/** - * drm_crtc_arm_vblank_event - arm vblank event after pageflip - * @crtc: the source CRTC of the vblank event - * @e: the event to send - * - * A lot of drivers need to generate vblank events for the very next vblank - * interrupt. For example when the page flip interrupt happens when the page - * flip gets armed, but not when it actually executes within the next vblank - * period. This helper function implements exactly the required vblank arming - * behaviour. - * - * NOTE: Drivers using this to send out the &drm_crtc_state.event as part of an - * atomic commit must ensure that the next vblank happens at exactly the same - * time as the atomic commit is committed to the hardware. This function itself - * does **not** protect again the next vblank interrupt racing with either this - * function call or the atomic commit operation. A possible sequence could be: - * - * 1. Driver commits new hardware state into vblank-synchronized registers. - * 2. A vblank happens, committing the hardware state. Also the corresponding - * vblank interrupt is fired off and fully processed by the interrupt - * handler. - * 3. The atomic commit operation proceeds to call drm_crtc_arm_vblank_event(). - * 4. The event is only send out for the next vblank, which is wrong. - * - * An equivalent race can happen when the driver calls - * drm_crtc_arm_vblank_event() before writing out the new hardware state. - * - * The only way to make this work safely is to prevent the vblank from firing - * (and the hardware from committing anything else) until the entire atomic - * commit sequence has run to completion. If the hardware does not have such a - * feature (e.g. using a "go" bit), then it is unsafe to use this functions. - * Instead drivers need to manually send out the event from their interrupt - * handler by calling drm_crtc_send_vblank_event() and make sure that there's no - * possible race with the hardware committing the atomic update. - * - * Caller must hold event lock. Caller must also hold a vblank reference for - * the event @e, which will be dropped when the next vblank arrives. - */ -void drm_crtc_arm_vblank_event(struct drm_crtc *crtc, - struct drm_pending_vblank_event *e) -{ - struct drm_device *dev = crtc->dev; - unsigned int pipe = drm_crtc_index(crtc); - - assert_spin_locked(&dev->event_lock); - - e->pipe = pipe; - e->event.sequence = drm_vblank_count(dev, pipe); - e->event.crtc_id = crtc->base.id; - list_add_tail(&e->base.link, &dev->vblank_event_list); -} -EXPORT_SYMBOL(drm_crtc_arm_vblank_event); - -/** - * drm_crtc_send_vblank_event - helper to send vblank event after pageflip - * @crtc: the source CRTC of the vblank event - * @e: the event to send - * - * Updates sequence # and timestamp on event for the most recently processed - * vblank, and sends it to userspace. Caller must hold event lock. - * - * See drm_crtc_arm_vblank_event() for a helper which can be used in certain - * situation, especially to send out events for atomic commit operations. - */ -void drm_crtc_send_vblank_event(struct drm_crtc *crtc, - struct drm_pending_vblank_event *e) -{ - struct drm_device *dev = crtc->dev; - unsigned int seq, pipe = drm_crtc_index(crtc); - struct timeval now; - - if (dev->num_crtcs > 0) { - seq = drm_vblank_count_and_time(dev, pipe, &now); - } else { - seq = 0; - - now = get_drm_timestamp(); - } - e->pipe = pipe; - e->event.crtc_id = crtc->base.id; - send_vblank_event(dev, e, seq, &now); -} -EXPORT_SYMBOL(drm_crtc_send_vblank_event); - -static int __enable_vblank(struct drm_device *dev, unsigned int pipe) -{ - if (drm_core_check_feature(dev, DRIVER_MODESET)) { - struct drm_crtc *crtc = drm_crtc_from_index(dev, pipe); - - if (crtc->funcs->enable_vblank) - return crtc->funcs->enable_vblank(crtc); - } - - return dev->driver->enable_vblank(dev, pipe); -} - -/** - * drm_vblank_enable - enable the vblank interrupt on a CRTC - * @dev: DRM device - * @pipe: CRTC index - * - * Returns: - * Zero on success or a negative error code on failure. - */ -static int drm_vblank_enable(struct drm_device *dev, unsigned int pipe) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - int ret = 0; - - assert_spin_locked(&dev->vbl_lock); - - spin_lock(&dev->vblank_time_lock); - - if (!vblank->enabled) { - /* - * Enable vblank irqs under vblank_time_lock protection. - * All vblank count & timestamp updates are held off - * until we are done reinitializing master counter and - * timestamps. Filtercode in drm_handle_vblank() will - * prevent double-accounting of same vblank interval. - */ - ret = __enable_vblank(dev, pipe); - DRM_DEBUG("enabling vblank on crtc %u, ret: %d\n", pipe, ret); - if (ret) { - atomic_dec(&vblank->refcount); - } else { - drm_update_vblank_count(dev, pipe, 0); - /* drm_update_vblank_count() includes a wmb so we just - * need to ensure that the compiler emits the write - * to mark the vblank as enabled after the call - * to drm_update_vblank_count(). - */ - WRITE_ONCE(vblank->enabled, true); - } - } - - spin_unlock(&dev->vblank_time_lock); - - return ret; -} - -/** - * drm_vblank_get - get a reference count on vblank events - * @dev: DRM device - * @pipe: index of CRTC to own - * - * Acquire a reference count on vblank events to avoid having them disabled - * while in use. - * - * This is the legacy version of drm_crtc_vblank_get(). - * - * Returns: - * Zero on success or a negative error code on failure. - */ -static int drm_vblank_get(struct drm_device *dev, unsigned int pipe) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - unsigned long irqflags; - int ret = 0; - - if (!dev->num_crtcs) - return -EINVAL; - - if (WARN_ON(pipe >= dev->num_crtcs)) - return -EINVAL; - - spin_lock_irqsave(&dev->vbl_lock, irqflags); - /* Going from 0->1 means we have to enable interrupts again */ - if (atomic_add_return(1, &vblank->refcount) == 1) { - ret = drm_vblank_enable(dev, pipe); - } else { - if (!vblank->enabled) { - atomic_dec(&vblank->refcount); - ret = -EINVAL; - } - } - spin_unlock_irqrestore(&dev->vbl_lock, irqflags); - - return ret; -} - -/** - * drm_crtc_vblank_get - get a reference count on vblank events - * @crtc: which CRTC to own - * - * Acquire a reference count on vblank events to avoid having them disabled - * while in use. - * - * Returns: - * Zero on success or a negative error code on failure. - */ -int drm_crtc_vblank_get(struct drm_crtc *crtc) -{ - return drm_vblank_get(crtc->dev, drm_crtc_index(crtc)); -} -EXPORT_SYMBOL(drm_crtc_vblank_get); - -/** - * drm_vblank_put - release ownership of vblank events - * @dev: DRM device - * @pipe: index of CRTC to release - * - * Release ownership of a given vblank counter, turning off interrupts - * if possible. Disable interrupts after drm_vblank_offdelay milliseconds. - * - * This is the legacy version of drm_crtc_vblank_put(). - */ -static void drm_vblank_put(struct drm_device *dev, unsigned int pipe) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - - if (WARN_ON(pipe >= dev->num_crtcs)) - return; - - if (WARN_ON(atomic_read(&vblank->refcount) == 0)) - return; - - /* Last user schedules interrupt disable */ - if (atomic_dec_and_test(&vblank->refcount)) { - if (drm_vblank_offdelay == 0) - return; - else if (drm_vblank_offdelay < 0) - vblank_disable_fn((unsigned long)vblank); - else if (!dev->vblank_disable_immediate) - mod_timer(&vblank->disable_timer, - jiffies + ((drm_vblank_offdelay * HZ)/1000)); - } -} - -/** - * drm_crtc_vblank_put - give up ownership of vblank events - * @crtc: which counter to give up - * - * Release ownership of a given vblank counter, turning off interrupts - * if possible. Disable interrupts after drm_vblank_offdelay milliseconds. - */ -void drm_crtc_vblank_put(struct drm_crtc *crtc) -{ - drm_vblank_put(crtc->dev, drm_crtc_index(crtc)); -} -EXPORT_SYMBOL(drm_crtc_vblank_put); - -/** - * drm_wait_one_vblank - wait for one vblank - * @dev: DRM device - * @pipe: CRTC index - * - * This waits for one vblank to pass on @pipe, using the irq driver interfaces. - * It is a failure to call this when the vblank irq for @pipe is disabled, e.g. - * due to lack of driver support or because the crtc is off. - */ -void drm_wait_one_vblank(struct drm_device *dev, unsigned int pipe) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - int ret; - u32 last; - - if (WARN_ON(pipe >= dev->num_crtcs)) - return; - - ret = drm_vblank_get(dev, pipe); - if (WARN(ret, "vblank not available on crtc %i, ret=%i\n", pipe, ret)) - return; - - last = drm_vblank_count(dev, pipe); - - ret = wait_event_timeout(vblank->queue, - last != drm_vblank_count(dev, pipe), - msecs_to_jiffies(100)); - - WARN(ret == 0, "vblank wait timed out on crtc %i\n", pipe); - - drm_vblank_put(dev, pipe); -} -EXPORT_SYMBOL(drm_wait_one_vblank); - -/** - * drm_crtc_wait_one_vblank - wait for one vblank - * @crtc: DRM crtc - * - * This waits for one vblank to pass on @crtc, using the irq driver interfaces. - * It is a failure to call this when the vblank irq for @crtc is disabled, e.g. - * due to lack of driver support or because the crtc is off. - */ -void drm_crtc_wait_one_vblank(struct drm_crtc *crtc) -{ - drm_wait_one_vblank(crtc->dev, drm_crtc_index(crtc)); -} -EXPORT_SYMBOL(drm_crtc_wait_one_vblank); - -/** - * drm_crtc_vblank_off - disable vblank events on a CRTC - * @crtc: CRTC in question - * - * Drivers can use this function to shut down the vblank interrupt handling when - * disabling a crtc. This function ensures that the latest vblank frame count is - * stored so that drm_vblank_on can restore it again. - * - * Drivers must use this function when the hardware vblank counter can get - * reset, e.g. when suspending. - */ -void drm_crtc_vblank_off(struct drm_crtc *crtc) -{ - struct drm_device *dev = crtc->dev; - unsigned int pipe = drm_crtc_index(crtc); - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - struct drm_pending_vblank_event *e, *t; - struct timeval now; - unsigned long irqflags; - unsigned int seq; - - if (WARN_ON(pipe >= dev->num_crtcs)) - return; - - spin_lock_irqsave(&dev->event_lock, irqflags); - - spin_lock(&dev->vbl_lock); - DRM_DEBUG_VBL("crtc %d, vblank enabled %d, inmodeset %d\n", - pipe, vblank->enabled, vblank->inmodeset); - - /* Avoid redundant vblank disables without previous - * drm_crtc_vblank_on(). */ - if (drm_core_check_feature(dev, DRIVER_ATOMIC) || !vblank->inmodeset) - vblank_disable_and_save(dev, pipe); - - wake_up(&vblank->queue); - - /* - * Prevent subsequent drm_vblank_get() from re-enabling - * the vblank interrupt by bumping the refcount. - */ - if (!vblank->inmodeset) { - atomic_inc(&vblank->refcount); - vblank->inmodeset = 1; - } - spin_unlock(&dev->vbl_lock); - - /* Send any queued vblank events, lest the natives grow disquiet */ - seq = drm_vblank_count_and_time(dev, pipe, &now); - - list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) { - if (e->pipe != pipe) - continue; - DRM_DEBUG("Sending premature vblank event on disable: " - "wanted %u, current %u\n", - e->event.sequence, seq); - list_del(&e->base.link); - drm_vblank_put(dev, pipe); - send_vblank_event(dev, e, seq, &now); - } - spin_unlock_irqrestore(&dev->event_lock, irqflags); - - /* Will be reset by the modeset helpers when re-enabling the crtc by - * calling drm_calc_timestamping_constants(). */ - vblank->hwmode.crtc_clock = 0; -} -EXPORT_SYMBOL(drm_crtc_vblank_off); - -/** - * drm_crtc_vblank_reset - reset vblank state to off on a CRTC - * @crtc: CRTC in question - * - * Drivers can use this function to reset the vblank state to off at load time. - * Drivers should use this together with the drm_crtc_vblank_off() and - * drm_crtc_vblank_on() functions. The difference compared to - * drm_crtc_vblank_off() is that this function doesn't save the vblank counter - * and hence doesn't need to call any driver hooks. - */ -void drm_crtc_vblank_reset(struct drm_crtc *crtc) -{ - struct drm_device *dev = crtc->dev; - unsigned long irqflags; - unsigned int pipe = drm_crtc_index(crtc); - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - - spin_lock_irqsave(&dev->vbl_lock, irqflags); - /* - * Prevent subsequent drm_vblank_get() from enabling the vblank - * interrupt by bumping the refcount. - */ - if (!vblank->inmodeset) { - atomic_inc(&vblank->refcount); - vblank->inmodeset = 1; - } - spin_unlock_irqrestore(&dev->vbl_lock, irqflags); - - WARN_ON(!list_empty(&dev->vblank_event_list)); -} -EXPORT_SYMBOL(drm_crtc_vblank_reset); - -/** - * drm_crtc_vblank_on - enable vblank events on a CRTC - * @crtc: CRTC in question - * - * This functions restores the vblank interrupt state captured with - * drm_crtc_vblank_off() again. Note that calls to drm_crtc_vblank_on() and - * drm_crtc_vblank_off() can be unbalanced and so can also be unconditionally called - * in driver load code to reflect the current hardware state of the crtc. - */ -void drm_crtc_vblank_on(struct drm_crtc *crtc) -{ - struct drm_device *dev = crtc->dev; - unsigned int pipe = drm_crtc_index(crtc); - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - unsigned long irqflags; - - if (WARN_ON(pipe >= dev->num_crtcs)) - return; - - spin_lock_irqsave(&dev->vbl_lock, irqflags); - DRM_DEBUG_VBL("crtc %d, vblank enabled %d, inmodeset %d\n", - pipe, vblank->enabled, vblank->inmodeset); - - /* Drop our private "prevent drm_vblank_get" refcount */ - if (vblank->inmodeset) { - atomic_dec(&vblank->refcount); - vblank->inmodeset = 0; - } - - drm_reset_vblank_timestamp(dev, pipe); - - /* - * re-enable interrupts if there are users left, or the - * user wishes vblank interrupts to be enabled all the time. - */ - if (atomic_read(&vblank->refcount) != 0 || drm_vblank_offdelay == 0) - WARN_ON(drm_vblank_enable(dev, pipe)); - spin_unlock_irqrestore(&dev->vbl_lock, irqflags); -} -EXPORT_SYMBOL(drm_crtc_vblank_on); - -static void drm_legacy_vblank_pre_modeset(struct drm_device *dev, - unsigned int pipe) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - - /* vblank is not initialized (IRQ not installed ?), or has been freed */ - if (!dev->num_crtcs) - return; - - if (WARN_ON(pipe >= dev->num_crtcs)) - return; - - /* - * To avoid all the problems that might happen if interrupts - * were enabled/disabled around or between these calls, we just - * have the kernel take a reference on the CRTC (just once though - * to avoid corrupting the count if multiple, mismatch calls occur), - * so that interrupts remain enabled in the interim. - */ - if (!vblank->inmodeset) { - vblank->inmodeset = 0x1; - if (drm_vblank_get(dev, pipe) == 0) - vblank->inmodeset |= 0x2; - } -} - -static void drm_legacy_vblank_post_modeset(struct drm_device *dev, - unsigned int pipe) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - unsigned long irqflags; - - /* vblank is not initialized (IRQ not installed ?), or has been freed */ - if (!dev->num_crtcs) - return; - - if (WARN_ON(pipe >= dev->num_crtcs)) - return; - - if (vblank->inmodeset) { - spin_lock_irqsave(&dev->vbl_lock, irqflags); - drm_reset_vblank_timestamp(dev, pipe); - spin_unlock_irqrestore(&dev->vbl_lock, irqflags); - - if (vblank->inmodeset & 0x2) - drm_vblank_put(dev, pipe); - - vblank->inmodeset = 0; - } -} - -int drm_legacy_modeset_ctl(struct drm_device *dev, void *data, - struct drm_file *file_priv) -{ - struct drm_modeset_ctl *modeset = data; - unsigned int pipe; - - /* If drm_vblank_init() hasn't been called yet, just no-op */ - if (!dev->num_crtcs) - return 0; - - /* KMS drivers handle this internally */ - if (!drm_core_check_feature(dev, DRIVER_LEGACY)) - return 0; - - pipe = modeset->crtc; - if (pipe >= dev->num_crtcs) - return -EINVAL; - - switch (modeset->cmd) { - case _DRM_PRE_MODESET: - drm_legacy_vblank_pre_modeset(dev, pipe); - break; - case _DRM_POST_MODESET: - drm_legacy_vblank_post_modeset(dev, pipe); - break; - default: - return -EINVAL; - } - - return 0; -} - -static inline bool vblank_passed(u32 seq, u32 ref) -{ - return (seq - ref) <= (1 << 23); -} - -static int drm_queue_vblank_event(struct drm_device *dev, unsigned int pipe, - union drm_wait_vblank *vblwait, - struct drm_file *file_priv) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - struct drm_pending_vblank_event *e; - struct timeval now; - unsigned long flags; - unsigned int seq; - int ret; - - e = kzalloc(sizeof(*e), GFP_KERNEL); - if (e == NULL) { - ret = -ENOMEM; - goto err_put; - } - - e->pipe = pipe; - e->event.base.type = DRM_EVENT_VBLANK; - e->event.base.length = sizeof(e->event); - e->event.user_data = vblwait->request.signal; - - spin_lock_irqsave(&dev->event_lock, flags); - - /* - * drm_crtc_vblank_off() might have been called after we called - * drm_vblank_get(). drm_crtc_vblank_off() holds event_lock around the - * vblank disable, so no need for further locking. The reference from - * drm_vblank_get() protects against vblank disable from another source. - */ - if (!READ_ONCE(vblank->enabled)) { - ret = -EINVAL; - goto err_unlock; - } - - ret = drm_event_reserve_init_locked(dev, file_priv, &e->base, - &e->event.base); - - if (ret) - goto err_unlock; - - seq = drm_vblank_count_and_time(dev, pipe, &now); - - DRM_DEBUG("event on vblank count %u, current %u, crtc %u\n", - vblwait->request.sequence, seq, pipe); - - trace_drm_vblank_event_queued(file_priv, pipe, - vblwait->request.sequence); - - e->event.sequence = vblwait->request.sequence; - if (vblank_passed(seq, vblwait->request.sequence)) { - drm_vblank_put(dev, pipe); - send_vblank_event(dev, e, seq, &now); - vblwait->reply.sequence = seq; - } else { - /* drm_handle_vblank_events will call drm_vblank_put */ - list_add_tail(&e->base.link, &dev->vblank_event_list); - vblwait->reply.sequence = vblwait->request.sequence; - } - - spin_unlock_irqrestore(&dev->event_lock, flags); - - return 0; - -err_unlock: - spin_unlock_irqrestore(&dev->event_lock, flags); - kfree(e); -err_put: - drm_vblank_put(dev, pipe); - return ret; -} - -static bool drm_wait_vblank_is_query(union drm_wait_vblank *vblwait) -{ - if (vblwait->request.sequence) - return false; - - return _DRM_VBLANK_RELATIVE == - (vblwait->request.type & (_DRM_VBLANK_TYPES_MASK | - _DRM_VBLANK_EVENT | - _DRM_VBLANK_NEXTONMISS)); -} - -/* - * Wait for VBLANK. - * - * \param inode device inode. - * \param file_priv DRM file private. - * \param cmd command. - * \param data user argument, pointing to a drm_wait_vblank structure. - * \return zero on success or a negative number on failure. - * - * This function enables the vblank interrupt on the pipe requested, then - * sleeps waiting for the requested sequence number to occur, and drops - * the vblank interrupt refcount afterwards. (vblank IRQ disable follows that - * after a timeout with no further vblank waits scheduled). - */ -int drm_wait_vblank(struct drm_device *dev, void *data, - struct drm_file *file_priv) -{ - struct drm_vblank_crtc *vblank; - union drm_wait_vblank *vblwait = data; - int ret; - unsigned int flags, seq, pipe, high_pipe; - - if (!dev->irq_enabled) - return -EINVAL; - - if (vblwait->request.type & _DRM_VBLANK_SIGNAL) - return -EINVAL; - - if (vblwait->request.type & - ~(_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK | - _DRM_VBLANK_HIGH_CRTC_MASK)) { - DRM_ERROR("Unsupported type value 0x%x, supported mask 0x%x\n", - vblwait->request.type, - (_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK | - _DRM_VBLANK_HIGH_CRTC_MASK)); - return -EINVAL; - } - - flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK; - high_pipe = (vblwait->request.type & _DRM_VBLANK_HIGH_CRTC_MASK); - if (high_pipe) - pipe = high_pipe >> _DRM_VBLANK_HIGH_CRTC_SHIFT; - else - pipe = flags & _DRM_VBLANK_SECONDARY ? 1 : 0; - if (pipe >= dev->num_crtcs) - return -EINVAL; - - vblank = &dev->vblank[pipe]; - - /* If the counter is currently enabled and accurate, short-circuit - * queries to return the cached timestamp of the last vblank. - */ - if (dev->vblank_disable_immediate && - drm_wait_vblank_is_query(vblwait) && - READ_ONCE(vblank->enabled)) { - struct timeval now; - - vblwait->reply.sequence = - drm_vblank_count_and_time(dev, pipe, &now); - vblwait->reply.tval_sec = now.tv_sec; - vblwait->reply.tval_usec = now.tv_usec; - return 0; - } - - ret = drm_vblank_get(dev, pipe); - if (ret) { - DRM_DEBUG("crtc %d failed to acquire vblank counter, %d\n", pipe, ret); - return ret; - } - seq = drm_vblank_count(dev, pipe); - - switch (vblwait->request.type & _DRM_VBLANK_TYPES_MASK) { - case _DRM_VBLANK_RELATIVE: - vblwait->request.sequence += seq; - vblwait->request.type &= ~_DRM_VBLANK_RELATIVE; - case _DRM_VBLANK_ABSOLUTE: - break; - default: - ret = -EINVAL; - goto done; - } - - if ((flags & _DRM_VBLANK_NEXTONMISS) && - vblank_passed(seq, vblwait->request.sequence)) - vblwait->request.sequence = seq + 1; - - if (flags & _DRM_VBLANK_EVENT) { - /* must hold on to the vblank ref until the event fires - * drm_vblank_put will be called asynchronously - */ - return drm_queue_vblank_event(dev, pipe, vblwait, file_priv); - } - - if (vblwait->request.sequence != seq) { - DRM_DEBUG("waiting on vblank count %u, crtc %u\n", - vblwait->request.sequence, pipe); - DRM_WAIT_ON(ret, vblank->queue, 3 * HZ, - vblank_passed(drm_vblank_count(dev, pipe), - vblwait->request.sequence) || - !READ_ONCE(vblank->enabled)); - } - - if (ret != -EINTR) { - struct timeval now; - - vblwait->reply.sequence = drm_vblank_count_and_time(dev, pipe, &now); - vblwait->reply.tval_sec = now.tv_sec; - vblwait->reply.tval_usec = now.tv_usec; - - DRM_DEBUG("crtc %d returning %u to client\n", - pipe, vblwait->reply.sequence); - } else { - DRM_DEBUG("crtc %d vblank wait interrupted by signal\n", pipe); - } - -done: - drm_vblank_put(dev, pipe); - return ret; -} - -static void drm_handle_vblank_events(struct drm_device *dev, unsigned int pipe) -{ - struct drm_pending_vblank_event *e, *t; - struct timeval now; - unsigned int seq; - - assert_spin_locked(&dev->event_lock); - - seq = drm_vblank_count_and_time(dev, pipe, &now); - - list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) { - if (e->pipe != pipe) - continue; - if (!vblank_passed(seq, e->event.sequence)) - continue; - - DRM_DEBUG("vblank event on %u, current %u\n", - e->event.sequence, seq); - - list_del(&e->base.link); - drm_vblank_put(dev, pipe); - send_vblank_event(dev, e, seq, &now); - } - - trace_drm_vblank_event(pipe, seq); -} - -/** - * drm_handle_vblank - handle a vblank event - * @dev: DRM device - * @pipe: index of CRTC where this event occurred - * - * Drivers should call this routine in their vblank interrupt handlers to - * update the vblank counter and send any signals that may be pending. - * - * This is the legacy version of drm_crtc_handle_vblank(). - */ -bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe) -{ - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; - unsigned long irqflags; - bool disable_irq; - - if (WARN_ON_ONCE(!dev->num_crtcs)) - return false; - - if (WARN_ON(pipe >= dev->num_crtcs)) - return false; - - spin_lock_irqsave(&dev->event_lock, irqflags); - - /* Need timestamp lock to prevent concurrent execution with - * vblank enable/disable, as this would cause inconsistent - * or corrupted timestamps and vblank counts. - */ - spin_lock(&dev->vblank_time_lock); - - /* Vblank irq handling disabled. Nothing to do. */ - if (!vblank->enabled) { - spin_unlock(&dev->vblank_time_lock); - spin_unlock_irqrestore(&dev->event_lock, irqflags); - return false; - } - - drm_update_vblank_count(dev, pipe, true); - - spin_unlock(&dev->vblank_time_lock); - - wake_up(&vblank->queue); - - /* With instant-off, we defer disabling the interrupt until after - * we finish processing the following vblank after all events have - * been signaled. The disable has to be last (after - * drm_handle_vblank_events) so that the timestamp is always accurate. - */ - disable_irq = (dev->vblank_disable_immediate && - drm_vblank_offdelay > 0 && - !atomic_read(&vblank->refcount)); - - drm_handle_vblank_events(dev, pipe); - - spin_unlock_irqrestore(&dev->event_lock, irqflags); - - if (disable_irq) - vblank_disable_fn((unsigned long)vblank); - - return true; -} -EXPORT_SYMBOL(drm_handle_vblank); - -/** - * drm_crtc_handle_vblank - handle a vblank event - * @crtc: where this event occurred - * - * Drivers should call this routine in their vblank interrupt handlers to - * update the vblank counter and send any signals that may be pending. - * - * This is the native KMS version of drm_handle_vblank(). - * - * Returns: - * True if the event was successfully handled, false on failure. - */ -bool drm_crtc_handle_vblank(struct drm_crtc *crtc) -{ - return drm_handle_vblank(crtc->dev, drm_crtc_index(crtc)); -} -EXPORT_SYMBOL(drm_crtc_handle_vblank); diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c new file mode 100644 index 0000000000000..630dc26379b70 --- /dev/null +++ b/drivers/gpu/drm/drm_vblank.c @@ -0,0 +1,1645 @@ +/* + * drm_irq.c IRQ and vblank support + * + * \author Rickard E. (Rik) Faith + * \author Gareth Hughes + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "drm_trace.h" +#include "drm_internal.h" + +/* Retry timestamp calculation up to 3 times to satisfy + * drm_timestamp_precision before giving up. + */ +#define DRM_TIMESTAMP_MAXRETRIES 3 + +/* Threshold in nanoseconds for detection of redundant + * vblank irq in drm_handle_vblank(). 1 msec should be ok. + */ +#define DRM_REDUNDANT_VBLIRQ_THRESH_NS 1000000 + +static bool +drm_get_last_vbltimestamp(struct drm_device *dev, unsigned int pipe, + struct timeval *tvblank, bool in_vblank_irq); + +static unsigned int drm_timestamp_precision = 20; /* Default to 20 usecs. */ + +/* + * Default to use monotonic timestamps for wait-for-vblank and page-flip + * complete events. + */ +unsigned int drm_timestamp_monotonic = 1; + +static int drm_vblank_offdelay = 5000; /* Default to 5000 msecs. */ + +module_param_named(vblankoffdelay, drm_vblank_offdelay, int, 0600); +module_param_named(timestamp_precision_usec, drm_timestamp_precision, int, 0600); +module_param_named(timestamp_monotonic, drm_timestamp_monotonic, int, 0600); +MODULE_PARM_DESC(vblankoffdelay, "Delay until vblank irq auto-disable [msecs] (0: never disable, <0: disable immediately)"); +MODULE_PARM_DESC(timestamp_precision_usec, "Max. error on timestamps [usecs]"); +MODULE_PARM_DESC(timestamp_monotonic, "Use monotonic timestamps"); + +static void store_vblank(struct drm_device *dev, unsigned int pipe, + u32 vblank_count_inc, + struct timeval *t_vblank, u32 last) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + + assert_spin_locked(&dev->vblank_time_lock); + + vblank->last = last; + + write_seqlock(&vblank->seqlock); + vblank->time = *t_vblank; + vblank->count += vblank_count_inc; + write_sequnlock(&vblank->seqlock); +} + +/* + * "No hw counter" fallback implementation of .get_vblank_counter() hook, + * if there is no useable hardware frame counter available. + */ +static u32 drm_vblank_no_hw_counter(struct drm_device *dev, unsigned int pipe) +{ + WARN_ON_ONCE(dev->max_vblank_count != 0); + return 0; +} + +static u32 __get_vblank_counter(struct drm_device *dev, unsigned int pipe) +{ + if (drm_core_check_feature(dev, DRIVER_MODESET)) { + struct drm_crtc *crtc = drm_crtc_from_index(dev, pipe); + + if (crtc->funcs->get_vblank_counter) + return crtc->funcs->get_vblank_counter(crtc); + } + + if (dev->driver->get_vblank_counter) + return dev->driver->get_vblank_counter(dev, pipe); + + return drm_vblank_no_hw_counter(dev, pipe); +} + +/* + * Reset the stored timestamp for the current vblank count to correspond + * to the last vblank occurred. + * + * Only to be called from drm_crtc_vblank_on(). + * + * Note: caller must hold &drm_device.vbl_lock since this reads & writes + * device vblank fields. + */ +static void drm_reset_vblank_timestamp(struct drm_device *dev, unsigned int pipe) +{ + u32 cur_vblank; + bool rc; + struct timeval t_vblank; + int count = DRM_TIMESTAMP_MAXRETRIES; + + spin_lock(&dev->vblank_time_lock); + + /* + * sample the current counter to avoid random jumps + * when drm_vblank_enable() applies the diff + */ + do { + cur_vblank = __get_vblank_counter(dev, pipe); + rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, false); + } while (cur_vblank != __get_vblank_counter(dev, pipe) && --count > 0); + + /* + * Only reinitialize corresponding vblank timestamp if high-precision query + * available and didn't fail. Otherwise reinitialize delayed at next vblank + * interrupt and assign 0 for now, to mark the vblanktimestamp as invalid. + */ + if (!rc) + t_vblank = (struct timeval) {0, 0}; + + /* + * +1 to make sure user will never see the same + * vblank counter value before and after a modeset + */ + store_vblank(dev, pipe, 1, &t_vblank, cur_vblank); + + spin_unlock(&dev->vblank_time_lock); +} + +/* + * Call back into the driver to update the appropriate vblank counter + * (specified by @pipe). Deal with wraparound, if it occurred, and + * update the last read value so we can deal with wraparound on the next + * call if necessary. + * + * Only necessary when going from off->on, to account for frames we + * didn't get an interrupt for. + * + * Note: caller must hold &drm_device.vbl_lock since this reads & writes + * device vblank fields. + */ +static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe, + bool in_vblank_irq) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + u32 cur_vblank, diff; + bool rc; + struct timeval t_vblank; + int count = DRM_TIMESTAMP_MAXRETRIES; + int framedur_ns = vblank->framedur_ns; + + /* + * Interrupts were disabled prior to this call, so deal with counter + * wrap if needed. + * NOTE! It's possible we lost a full dev->max_vblank_count + 1 events + * here if the register is small or we had vblank interrupts off for + * a long time. + * + * We repeat the hardware vblank counter & timestamp query until + * we get consistent results. This to prevent races between gpu + * updating its hardware counter while we are retrieving the + * corresponding vblank timestamp. + */ + do { + cur_vblank = __get_vblank_counter(dev, pipe); + rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, in_vblank_irq); + } while (cur_vblank != __get_vblank_counter(dev, pipe) && --count > 0); + + if (dev->max_vblank_count != 0) { + /* trust the hw counter when it's around */ + diff = (cur_vblank - vblank->last) & dev->max_vblank_count; + } else if (rc && framedur_ns) { + const struct timeval *t_old; + u64 diff_ns; + + t_old = &vblank->time; + diff_ns = timeval_to_ns(&t_vblank) - timeval_to_ns(t_old); + + /* + * Figure out how many vblanks we've missed based + * on the difference in the timestamps and the + * frame/field duration. + */ + diff = DIV_ROUND_CLOSEST_ULL(diff_ns, framedur_ns); + + if (diff == 0 && in_vblank_irq) + DRM_DEBUG_VBL("crtc %u: Redundant vblirq ignored." + " diff_ns = %lld, framedur_ns = %d)\n", + pipe, (long long) diff_ns, framedur_ns); + } else { + /* some kind of default for drivers w/o accurate vbl timestamping */ + diff = in_vblank_irq ? 1 : 0; + } + + /* + * Within a drm_vblank_pre_modeset - drm_vblank_post_modeset + * interval? If so then vblank irqs keep running and it will likely + * happen that the hardware vblank counter is not trustworthy as it + * might reset at some point in that interval and vblank timestamps + * are not trustworthy either in that interval. Iow. this can result + * in a bogus diff >> 1 which must be avoided as it would cause + * random large forward jumps of the software vblank counter. + */ + if (diff > 1 && (vblank->inmodeset & 0x2)) { + DRM_DEBUG_VBL("clamping vblank bump to 1 on crtc %u: diffr=%u" + " due to pre-modeset.\n", pipe, diff); + diff = 1; + } + + DRM_DEBUG_VBL("updating vblank count on crtc %u:" + " current=%u, diff=%u, hw=%u hw_last=%u\n", + pipe, vblank->count, diff, cur_vblank, vblank->last); + + if (diff == 0) { + WARN_ON_ONCE(cur_vblank != vblank->last); + return; + } + + /* + * Only reinitialize corresponding vblank timestamp if high-precision query + * available and didn't fail, or we were called from the vblank interrupt. + * Otherwise reinitialize delayed at next vblank interrupt and assign 0 + * for now, to mark the vblanktimestamp as invalid. + */ + if (!rc && in_vblank_irq) + t_vblank = (struct timeval) {0, 0}; + + store_vblank(dev, pipe, diff, &t_vblank, cur_vblank); +} + +static u32 drm_vblank_count(struct drm_device *dev, unsigned int pipe) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return 0; + + return vblank->count; +} + +/** + * drm_accurate_vblank_count - retrieve the master vblank counter + * @crtc: which counter to retrieve + * + * This function is similar to @drm_crtc_vblank_count but this + * function interpolates to handle a race with vblank irq's. + * + * This is mostly useful for hardware that can obtain the scanout + * position, but doesn't have a frame counter. + */ +u32 drm_accurate_vblank_count(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + unsigned int pipe = drm_crtc_index(crtc); + u32 vblank; + unsigned long flags; + + WARN(!dev->driver->get_vblank_timestamp, + "This function requires support for accurate vblank timestamps."); + + spin_lock_irqsave(&dev->vblank_time_lock, flags); + + drm_update_vblank_count(dev, pipe, false); + vblank = drm_vblank_count(dev, pipe); + + spin_unlock_irqrestore(&dev->vblank_time_lock, flags); + + return vblank; +} +EXPORT_SYMBOL(drm_accurate_vblank_count); + +static void __disable_vblank(struct drm_device *dev, unsigned int pipe) +{ + if (drm_core_check_feature(dev, DRIVER_MODESET)) { + struct drm_crtc *crtc = drm_crtc_from_index(dev, pipe); + + if (crtc->funcs->disable_vblank) { + crtc->funcs->disable_vblank(crtc); + return; + } + } + + dev->driver->disable_vblank(dev, pipe); +} + +/* + * Disable vblank irq's on crtc, make sure that last vblank count + * of hardware and corresponding consistent software vblank counter + * are preserved, even if there are any spurious vblank irq's after + * disable. + */ +void drm_vblank_disable_and_save(struct drm_device *dev, unsigned int pipe) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + unsigned long irqflags; + + assert_spin_locked(&dev->vbl_lock); + + /* Prevent vblank irq processing while disabling vblank irqs, + * so no updates of timestamps or count can happen after we've + * disabled. Needed to prevent races in case of delayed irq's. + */ + spin_lock_irqsave(&dev->vblank_time_lock, irqflags); + + /* + * Only disable vblank interrupts if they're enabled. This avoids + * calling the ->disable_vblank() operation in atomic context with the + * hardware potentially runtime suspended. + */ + if (vblank->enabled) { + __disable_vblank(dev, pipe); + vblank->enabled = false; + } + + /* + * Always update the count and timestamp to maintain the + * appearance that the counter has been ticking all along until + * this time. This makes the count account for the entire time + * between drm_crtc_vblank_on() and drm_crtc_vblank_off(). + */ + drm_update_vblank_count(dev, pipe, false); + + spin_unlock_irqrestore(&dev->vblank_time_lock, irqflags); +} + +static void vblank_disable_fn(unsigned long arg) +{ + struct drm_vblank_crtc *vblank = (void *)arg; + struct drm_device *dev = vblank->dev; + unsigned int pipe = vblank->pipe; + unsigned long irqflags; + + spin_lock_irqsave(&dev->vbl_lock, irqflags); + if (atomic_read(&vblank->refcount) == 0 && vblank->enabled) { + DRM_DEBUG("disabling vblank on crtc %u\n", pipe); + drm_vblank_disable_and_save(dev, pipe); + } + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); +} + +/** + * drm_vblank_cleanup - cleanup vblank support + * @dev: DRM device + * + * This function cleans up any resources allocated in drm_vblank_init. + */ +void drm_vblank_cleanup(struct drm_device *dev) +{ + unsigned int pipe; + + /* Bail if the driver didn't call drm_vblank_init() */ + if (dev->num_crtcs == 0) + return; + + for (pipe = 0; pipe < dev->num_crtcs; pipe++) { + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + + WARN_ON(READ_ONCE(vblank->enabled) && + drm_core_check_feature(dev, DRIVER_MODESET)); + + del_timer_sync(&vblank->disable_timer); + } + + kfree(dev->vblank); + + dev->num_crtcs = 0; +} +EXPORT_SYMBOL(drm_vblank_cleanup); + +/** + * drm_vblank_init - initialize vblank support + * @dev: DRM device + * @num_crtcs: number of CRTCs supported by @dev + * + * This function initializes vblank support for @num_crtcs display pipelines. + * + * Returns: + * Zero on success or a negative error code on failure. + */ +int drm_vblank_init(struct drm_device *dev, unsigned int num_crtcs) +{ + int ret = -ENOMEM; + unsigned int i; + + spin_lock_init(&dev->vbl_lock); + spin_lock_init(&dev->vblank_time_lock); + + dev->num_crtcs = num_crtcs; + + dev->vblank = kcalloc(num_crtcs, sizeof(*dev->vblank), GFP_KERNEL); + if (!dev->vblank) + goto err; + + for (i = 0; i < num_crtcs; i++) { + struct drm_vblank_crtc *vblank = &dev->vblank[i]; + + vblank->dev = dev; + vblank->pipe = i; + init_waitqueue_head(&vblank->queue); + setup_timer(&vblank->disable_timer, vblank_disable_fn, + (unsigned long)vblank); + seqlock_init(&vblank->seqlock); + } + + DRM_INFO("Supports vblank timestamp caching Rev 2 (21.10.2013).\n"); + + /* Driver specific high-precision vblank timestamping supported? */ + if (dev->driver->get_vblank_timestamp) + DRM_INFO("Driver supports precise vblank timestamp query.\n"); + else + DRM_INFO("No driver support for vblank timestamp query.\n"); + + /* Must have precise timestamping for reliable vblank instant disable */ + if (dev->vblank_disable_immediate && !dev->driver->get_vblank_timestamp) { + dev->vblank_disable_immediate = false; + DRM_INFO("Setting vblank_disable_immediate to false because " + "get_vblank_timestamp == NULL\n"); + } + + return 0; + +err: + dev->num_crtcs = 0; + return ret; +} +EXPORT_SYMBOL(drm_vblank_init); + +/** + * drm_crtc_vblank_waitqueue - get vblank waitqueue for the CRTC + * @crtc: which CRTC's vblank waitqueue to retrieve + * + * This function returns a pointer to the vblank waitqueue for the CRTC. + * Drivers can use this to implement vblank waits using wait_event() and related + * functions. + */ +wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc) +{ + return &crtc->dev->vblank[drm_crtc_index(crtc)].queue; +} +EXPORT_SYMBOL(drm_crtc_vblank_waitqueue); + + +/** + * drm_calc_timestamping_constants - calculate vblank timestamp constants + * @crtc: drm_crtc whose timestamp constants should be updated. + * @mode: display mode containing the scanout timings + * + * Calculate and store various constants which are later + * needed by vblank and swap-completion timestamping, e.g, + * by drm_calc_vbltimestamp_from_scanoutpos(). They are + * derived from CRTC's true scanout timing, so they take + * things like panel scaling or other adjustments into account. + */ +void drm_calc_timestamping_constants(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct drm_device *dev = crtc->dev; + unsigned int pipe = drm_crtc_index(crtc); + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + int linedur_ns = 0, framedur_ns = 0; + int dotclock = mode->crtc_clock; + + if (!dev->num_crtcs) + return; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + + /* Valid dotclock? */ + if (dotclock > 0) { + int frame_size = mode->crtc_htotal * mode->crtc_vtotal; + + /* + * Convert scanline length in pixels and video + * dot clock to line duration and frame duration + * in nanoseconds: + */ + linedur_ns = div_u64((u64) mode->crtc_htotal * 1000000, dotclock); + framedur_ns = div_u64((u64) frame_size * 1000000, dotclock); + + /* + * Fields of interlaced scanout modes are only half a frame duration. + */ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + framedur_ns /= 2; + } else + DRM_ERROR("crtc %u: Can't calculate constants, dotclock = 0!\n", + crtc->base.id); + + vblank->linedur_ns = linedur_ns; + vblank->framedur_ns = framedur_ns; + vblank->hwmode = *mode; + + DRM_DEBUG("crtc %u: hwmode: htotal %d, vtotal %d, vdisplay %d\n", + crtc->base.id, mode->crtc_htotal, + mode->crtc_vtotal, mode->crtc_vdisplay); + DRM_DEBUG("crtc %u: clock %d kHz framedur %d linedur %d\n", + crtc->base.id, dotclock, framedur_ns, linedur_ns); +} +EXPORT_SYMBOL(drm_calc_timestamping_constants); + +/** + * drm_calc_vbltimestamp_from_scanoutpos - precise vblank timestamp helper + * @dev: DRM device + * @pipe: index of CRTC whose vblank timestamp to retrieve + * @max_error: Desired maximum allowable error in timestamps (nanosecs) + * On return contains true maximum error of timestamp + * @vblank_time: Pointer to struct timeval which should receive the timestamp + * @in_vblank_irq: + * True when called from drm_crtc_handle_vblank(). Some drivers + * need to apply some workarounds for gpu-specific vblank irq quirks + * if flag is set. + * + * Implements calculation of exact vblank timestamps from given drm_display_mode + * timings and current video scanout position of a CRTC. This can be called from + * within get_vblank_timestamp() implementation of a kms driver to implement the + * actual timestamping. + * + * Should return timestamps conforming to the OML_sync_control OpenML + * extension specification. The timestamp corresponds to the end of + * the vblank interval, aka start of scanout of topmost-leftmost display + * pixel in the following video frame. + * + * Requires support for optional dev->driver->get_scanout_position() + * in kms driver, plus a bit of setup code to provide a drm_display_mode + * that corresponds to the true scanout timing. + * + * The current implementation only handles standard video modes. It + * returns as no operation if a doublescan or interlaced video mode is + * active. Higher level code is expected to handle this. + * + * This function can be used to implement the &drm_driver.get_vblank_timestamp + * directly, if the driver implements the &drm_driver.get_scanout_position hook. + * + * Note that atomic drivers must call drm_calc_timestamping_constants() before + * enabling a CRTC. The atomic helpers already take care of that in + * drm_atomic_helper_update_legacy_modeset_state(). + * + * Returns: + * + * Returns true on success, and false on failure, i.e. when no accurate + * timestamp could be acquired. + */ +bool drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, + unsigned int pipe, + int *max_error, + struct timeval *vblank_time, + bool in_vblank_irq) +{ + struct timeval tv_etime; + ktime_t stime, etime; + bool vbl_status; + struct drm_crtc *crtc; + const struct drm_display_mode *mode; + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + int vpos, hpos, i; + int delta_ns, duration_ns; + + if (!drm_core_check_feature(dev, DRIVER_MODESET)) + return false; + + crtc = drm_crtc_from_index(dev, pipe); + + if (pipe >= dev->num_crtcs || !crtc) { + DRM_ERROR("Invalid crtc %u\n", pipe); + return false; + } + + /* Scanout position query not supported? Should not happen. */ + if (!dev->driver->get_scanout_position) { + DRM_ERROR("Called from driver w/o get_scanout_position()!?\n"); + return false; + } + + if (drm_drv_uses_atomic_modeset(dev)) + mode = &vblank->hwmode; + else + mode = &crtc->hwmode; + + /* If mode timing undefined, just return as no-op: + * Happens during initial modesetting of a crtc. + */ + if (mode->crtc_clock == 0) { + DRM_DEBUG("crtc %u: Noop due to uninitialized mode.\n", pipe); + WARN_ON_ONCE(drm_drv_uses_atomic_modeset(dev)); + + return false; + } + + /* Get current scanout position with system timestamp. + * Repeat query up to DRM_TIMESTAMP_MAXRETRIES times + * if single query takes longer than max_error nanoseconds. + * + * This guarantees a tight bound on maximum error if + * code gets preempted or delayed for some reason. + */ + for (i = 0; i < DRM_TIMESTAMP_MAXRETRIES; i++) { + /* + * Get vertical and horizontal scanout position vpos, hpos, + * and bounding timestamps stime, etime, pre/post query. + */ + vbl_status = dev->driver->get_scanout_position(dev, pipe, + in_vblank_irq, + &vpos, &hpos, + &stime, &etime, + mode); + + /* Return as no-op if scanout query unsupported or failed. */ + if (!vbl_status) { + DRM_DEBUG("crtc %u : scanoutpos query failed.\n", + pipe); + return false; + } + + /* Compute uncertainty in timestamp of scanout position query. */ + duration_ns = ktime_to_ns(etime) - ktime_to_ns(stime); + + /* Accept result with < max_error nsecs timing uncertainty. */ + if (duration_ns <= *max_error) + break; + } + + /* Noisy system timing? */ + if (i == DRM_TIMESTAMP_MAXRETRIES) { + DRM_DEBUG("crtc %u: Noisy timestamp %d us > %d us [%d reps].\n", + pipe, duration_ns/1000, *max_error/1000, i); + } + + /* Return upper bound of timestamp precision error. */ + *max_error = duration_ns; + + /* Convert scanout position into elapsed time at raw_time query + * since start of scanout at first display scanline. delta_ns + * can be negative if start of scanout hasn't happened yet. + */ + delta_ns = div_s64(1000000LL * (vpos * mode->crtc_htotal + hpos), + mode->crtc_clock); + + if (!drm_timestamp_monotonic) + etime = ktime_mono_to_real(etime); + + /* save this only for debugging purposes */ + tv_etime = ktime_to_timeval(etime); + /* Subtract time delta from raw timestamp to get final + * vblank_time timestamp for end of vblank. + */ + etime = ktime_sub_ns(etime, delta_ns); + *vblank_time = ktime_to_timeval(etime); + + DRM_DEBUG_VBL("crtc %u : v p(%d,%d)@ %ld.%ld -> %ld.%ld [e %d us, %d rep]\n", + pipe, hpos, vpos, + (long)tv_etime.tv_sec, (long)tv_etime.tv_usec, + (long)vblank_time->tv_sec, (long)vblank_time->tv_usec, + duration_ns/1000, i); + + return true; +} +EXPORT_SYMBOL(drm_calc_vbltimestamp_from_scanoutpos); + +static struct timeval get_drm_timestamp(void) +{ + ktime_t now; + + now = drm_timestamp_monotonic ? ktime_get() : ktime_get_real(); + return ktime_to_timeval(now); +} + +/** + * drm_get_last_vbltimestamp - retrieve raw timestamp for the most recent + * vblank interval + * @dev: DRM device + * @pipe: index of CRTC whose vblank timestamp to retrieve + * @tvblank: Pointer to target struct timeval which should receive the timestamp + * @in_vblank_irq: + * True when called from drm_crtc_handle_vblank(). Some drivers + * need to apply some workarounds for gpu-specific vblank irq quirks + * if flag is set. + * + * Fetches the system timestamp corresponding to the time of the most recent + * vblank interval on specified CRTC. May call into kms-driver to + * compute the timestamp with a high-precision GPU specific method. + * + * Returns zero if timestamp originates from uncorrected do_gettimeofday() + * call, i.e., it isn't very precisely locked to the true vblank. + * + * Returns: + * True if timestamp is considered to be very precise, false otherwise. + */ +static bool +drm_get_last_vbltimestamp(struct drm_device *dev, unsigned int pipe, + struct timeval *tvblank, bool in_vblank_irq) +{ + bool ret = false; + + /* Define requested maximum error on timestamps (nanoseconds). */ + int max_error = (int) drm_timestamp_precision * 1000; + + /* Query driver if possible and precision timestamping enabled. */ + if (dev->driver->get_vblank_timestamp && (max_error > 0)) + ret = dev->driver->get_vblank_timestamp(dev, pipe, &max_error, + tvblank, in_vblank_irq); + + /* GPU high precision timestamp query unsupported or failed. + * Return current monotonic/gettimeofday timestamp as best estimate. + */ + if (!ret) + *tvblank = get_drm_timestamp(); + + return ret; +} + +/** + * drm_crtc_vblank_count - retrieve "cooked" vblank counter value + * @crtc: which counter to retrieve + * + * Fetches the "cooked" vblank count value that represents the number of + * vblank events since the system was booted, including lost events due to + * modesetting activity. + * + * Returns: + * The software vblank counter. + */ +u32 drm_crtc_vblank_count(struct drm_crtc *crtc) +{ + return drm_vblank_count(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_vblank_count); + +/** + * drm_vblank_count_and_time - retrieve "cooked" vblank counter value and the + * system timestamp corresponding to that vblank counter value. + * @dev: DRM device + * @pipe: index of CRTC whose counter to retrieve + * @vblanktime: Pointer to struct timeval to receive the vblank timestamp. + * + * Fetches the "cooked" vblank count value that represents the number of + * vblank events since the system was booted, including lost events due to + * modesetting activity. Returns corresponding system timestamp of the time + * of the vblank interval that corresponds to the current vblank counter value. + * + * This is the legacy version of drm_crtc_vblank_count_and_time(). + */ +static u32 drm_vblank_count_and_time(struct drm_device *dev, unsigned int pipe, + struct timeval *vblanktime) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + u32 vblank_count; + unsigned int seq; + + if (WARN_ON(pipe >= dev->num_crtcs)) { + *vblanktime = (struct timeval) { 0 }; + return 0; + } + + do { + seq = read_seqbegin(&vblank->seqlock); + vblank_count = vblank->count; + *vblanktime = vblank->time; + } while (read_seqretry(&vblank->seqlock, seq)); + + return vblank_count; +} + +/** + * drm_crtc_vblank_count_and_time - retrieve "cooked" vblank counter value + * and the system timestamp corresponding to that vblank counter value + * @crtc: which counter to retrieve + * @vblanktime: Pointer to struct timeval to receive the vblank timestamp. + * + * Fetches the "cooked" vblank count value that represents the number of + * vblank events since the system was booted, including lost events due to + * modesetting activity. Returns corresponding system timestamp of the time + * of the vblank interval that corresponds to the current vblank counter value. + */ +u32 drm_crtc_vblank_count_and_time(struct drm_crtc *crtc, + struct timeval *vblanktime) +{ + return drm_vblank_count_and_time(crtc->dev, drm_crtc_index(crtc), + vblanktime); +} +EXPORT_SYMBOL(drm_crtc_vblank_count_and_time); + +static void send_vblank_event(struct drm_device *dev, + struct drm_pending_vblank_event *e, + unsigned long seq, struct timeval *now) +{ + e->event.sequence = seq; + e->event.tv_sec = now->tv_sec; + e->event.tv_usec = now->tv_usec; + + trace_drm_vblank_event_delivered(e->base.file_priv, e->pipe, + e->event.sequence); + + drm_send_event_locked(dev, &e->base); +} + +/** + * drm_crtc_arm_vblank_event - arm vblank event after pageflip + * @crtc: the source CRTC of the vblank event + * @e: the event to send + * + * A lot of drivers need to generate vblank events for the very next vblank + * interrupt. For example when the page flip interrupt happens when the page + * flip gets armed, but not when it actually executes within the next vblank + * period. This helper function implements exactly the required vblank arming + * behaviour. + * + * NOTE: Drivers using this to send out the &drm_crtc_state.event as part of an + * atomic commit must ensure that the next vblank happens at exactly the same + * time as the atomic commit is committed to the hardware. This function itself + * does **not** protect again the next vblank interrupt racing with either this + * function call or the atomic commit operation. A possible sequence could be: + * + * 1. Driver commits new hardware state into vblank-synchronized registers. + * 2. A vblank happens, committing the hardware state. Also the corresponding + * vblank interrupt is fired off and fully processed by the interrupt + * handler. + * 3. The atomic commit operation proceeds to call drm_crtc_arm_vblank_event(). + * 4. The event is only send out for the next vblank, which is wrong. + * + * An equivalent race can happen when the driver calls + * drm_crtc_arm_vblank_event() before writing out the new hardware state. + * + * The only way to make this work safely is to prevent the vblank from firing + * (and the hardware from committing anything else) until the entire atomic + * commit sequence has run to completion. If the hardware does not have such a + * feature (e.g. using a "go" bit), then it is unsafe to use this functions. + * Instead drivers need to manually send out the event from their interrupt + * handler by calling drm_crtc_send_vblank_event() and make sure that there's no + * possible race with the hardware committing the atomic update. + * + * Caller must hold event lock. Caller must also hold a vblank reference for + * the event @e, which will be dropped when the next vblank arrives. + */ +void drm_crtc_arm_vblank_event(struct drm_crtc *crtc, + struct drm_pending_vblank_event *e) +{ + struct drm_device *dev = crtc->dev; + unsigned int pipe = drm_crtc_index(crtc); + + assert_spin_locked(&dev->event_lock); + + e->pipe = pipe; + e->event.sequence = drm_vblank_count(dev, pipe); + e->event.crtc_id = crtc->base.id; + list_add_tail(&e->base.link, &dev->vblank_event_list); +} +EXPORT_SYMBOL(drm_crtc_arm_vblank_event); + +/** + * drm_crtc_send_vblank_event - helper to send vblank event after pageflip + * @crtc: the source CRTC of the vblank event + * @e: the event to send + * + * Updates sequence # and timestamp on event for the most recently processed + * vblank, and sends it to userspace. Caller must hold event lock. + * + * See drm_crtc_arm_vblank_event() for a helper which can be used in certain + * situation, especially to send out events for atomic commit operations. + */ +void drm_crtc_send_vblank_event(struct drm_crtc *crtc, + struct drm_pending_vblank_event *e) +{ + struct drm_device *dev = crtc->dev; + unsigned int seq, pipe = drm_crtc_index(crtc); + struct timeval now; + + if (dev->num_crtcs > 0) { + seq = drm_vblank_count_and_time(dev, pipe, &now); + } else { + seq = 0; + + now = get_drm_timestamp(); + } + e->pipe = pipe; + e->event.crtc_id = crtc->base.id; + send_vblank_event(dev, e, seq, &now); +} +EXPORT_SYMBOL(drm_crtc_send_vblank_event); + +static int __enable_vblank(struct drm_device *dev, unsigned int pipe) +{ + if (drm_core_check_feature(dev, DRIVER_MODESET)) { + struct drm_crtc *crtc = drm_crtc_from_index(dev, pipe); + + if (crtc->funcs->enable_vblank) + return crtc->funcs->enable_vblank(crtc); + } + + return dev->driver->enable_vblank(dev, pipe); +} + +/** + * drm_vblank_enable - enable the vblank interrupt on a CRTC + * @dev: DRM device + * @pipe: CRTC index + * + * Returns: + * Zero on success or a negative error code on failure. + */ +static int drm_vblank_enable(struct drm_device *dev, unsigned int pipe) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + int ret = 0; + + assert_spin_locked(&dev->vbl_lock); + + spin_lock(&dev->vblank_time_lock); + + if (!vblank->enabled) { + /* + * Enable vblank irqs under vblank_time_lock protection. + * All vblank count & timestamp updates are held off + * until we are done reinitializing master counter and + * timestamps. Filtercode in drm_handle_vblank() will + * prevent double-accounting of same vblank interval. + */ + ret = __enable_vblank(dev, pipe); + DRM_DEBUG("enabling vblank on crtc %u, ret: %d\n", pipe, ret); + if (ret) { + atomic_dec(&vblank->refcount); + } else { + drm_update_vblank_count(dev, pipe, 0); + /* drm_update_vblank_count() includes a wmb so we just + * need to ensure that the compiler emits the write + * to mark the vblank as enabled after the call + * to drm_update_vblank_count(). + */ + WRITE_ONCE(vblank->enabled, true); + } + } + + spin_unlock(&dev->vblank_time_lock); + + return ret; +} + +/** + * drm_vblank_get - get a reference count on vblank events + * @dev: DRM device + * @pipe: index of CRTC to own + * + * Acquire a reference count on vblank events to avoid having them disabled + * while in use. + * + * This is the legacy version of drm_crtc_vblank_get(). + * + * Returns: + * Zero on success or a negative error code on failure. + */ +static int drm_vblank_get(struct drm_device *dev, unsigned int pipe) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + unsigned long irqflags; + int ret = 0; + + if (!dev->num_crtcs) + return -EINVAL; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return -EINVAL; + + spin_lock_irqsave(&dev->vbl_lock, irqflags); + /* Going from 0->1 means we have to enable interrupts again */ + if (atomic_add_return(1, &vblank->refcount) == 1) { + ret = drm_vblank_enable(dev, pipe); + } else { + if (!vblank->enabled) { + atomic_dec(&vblank->refcount); + ret = -EINVAL; + } + } + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); + + return ret; +} + +/** + * drm_crtc_vblank_get - get a reference count on vblank events + * @crtc: which CRTC to own + * + * Acquire a reference count on vblank events to avoid having them disabled + * while in use. + * + * Returns: + * Zero on success or a negative error code on failure. + */ +int drm_crtc_vblank_get(struct drm_crtc *crtc) +{ + return drm_vblank_get(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_vblank_get); + +/** + * drm_vblank_put - release ownership of vblank events + * @dev: DRM device + * @pipe: index of CRTC to release + * + * Release ownership of a given vblank counter, turning off interrupts + * if possible. Disable interrupts after drm_vblank_offdelay milliseconds. + * + * This is the legacy version of drm_crtc_vblank_put(). + */ +static void drm_vblank_put(struct drm_device *dev, unsigned int pipe) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + + if (WARN_ON(atomic_read(&vblank->refcount) == 0)) + return; + + /* Last user schedules interrupt disable */ + if (atomic_dec_and_test(&vblank->refcount)) { + if (drm_vblank_offdelay == 0) + return; + else if (drm_vblank_offdelay < 0) + vblank_disable_fn((unsigned long)vblank); + else if (!dev->vblank_disable_immediate) + mod_timer(&vblank->disable_timer, + jiffies + ((drm_vblank_offdelay * HZ)/1000)); + } +} + +/** + * drm_crtc_vblank_put - give up ownership of vblank events + * @crtc: which counter to give up + * + * Release ownership of a given vblank counter, turning off interrupts + * if possible. Disable interrupts after drm_vblank_offdelay milliseconds. + */ +void drm_crtc_vblank_put(struct drm_crtc *crtc) +{ + drm_vblank_put(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_vblank_put); + +/** + * drm_wait_one_vblank - wait for one vblank + * @dev: DRM device + * @pipe: CRTC index + * + * This waits for one vblank to pass on @pipe, using the irq driver interfaces. + * It is a failure to call this when the vblank irq for @pipe is disabled, e.g. + * due to lack of driver support or because the crtc is off. + */ +void drm_wait_one_vblank(struct drm_device *dev, unsigned int pipe) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + int ret; + u32 last; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + + ret = drm_vblank_get(dev, pipe); + if (WARN(ret, "vblank not available on crtc %i, ret=%i\n", pipe, ret)) + return; + + last = drm_vblank_count(dev, pipe); + + ret = wait_event_timeout(vblank->queue, + last != drm_vblank_count(dev, pipe), + msecs_to_jiffies(100)); + + WARN(ret == 0, "vblank wait timed out on crtc %i\n", pipe); + + drm_vblank_put(dev, pipe); +} +EXPORT_SYMBOL(drm_wait_one_vblank); + +/** + * drm_crtc_wait_one_vblank - wait for one vblank + * @crtc: DRM crtc + * + * This waits for one vblank to pass on @crtc, using the irq driver interfaces. + * It is a failure to call this when the vblank irq for @crtc is disabled, e.g. + * due to lack of driver support or because the crtc is off. + */ +void drm_crtc_wait_one_vblank(struct drm_crtc *crtc) +{ + drm_wait_one_vblank(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_wait_one_vblank); + +/** + * drm_crtc_vblank_off - disable vblank events on a CRTC + * @crtc: CRTC in question + * + * Drivers can use this function to shut down the vblank interrupt handling when + * disabling a crtc. This function ensures that the latest vblank frame count is + * stored so that drm_vblank_on can restore it again. + * + * Drivers must use this function when the hardware vblank counter can get + * reset, e.g. when suspending. + */ +void drm_crtc_vblank_off(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + unsigned int pipe = drm_crtc_index(crtc); + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + struct drm_pending_vblank_event *e, *t; + struct timeval now; + unsigned long irqflags; + unsigned int seq; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + + spin_lock_irqsave(&dev->event_lock, irqflags); + + spin_lock(&dev->vbl_lock); + DRM_DEBUG_VBL("crtc %d, vblank enabled %d, inmodeset %d\n", + pipe, vblank->enabled, vblank->inmodeset); + + /* Avoid redundant vblank disables without previous + * drm_crtc_vblank_on(). */ + if (drm_core_check_feature(dev, DRIVER_ATOMIC) || !vblank->inmodeset) + drm_vblank_disable_and_save(dev, pipe); + + wake_up(&vblank->queue); + + /* + * Prevent subsequent drm_vblank_get() from re-enabling + * the vblank interrupt by bumping the refcount. + */ + if (!vblank->inmodeset) { + atomic_inc(&vblank->refcount); + vblank->inmodeset = 1; + } + spin_unlock(&dev->vbl_lock); + + /* Send any queued vblank events, lest the natives grow disquiet */ + seq = drm_vblank_count_and_time(dev, pipe, &now); + + list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) { + if (e->pipe != pipe) + continue; + DRM_DEBUG("Sending premature vblank event on disable: " + "wanted %u, current %u\n", + e->event.sequence, seq); + list_del(&e->base.link); + drm_vblank_put(dev, pipe); + send_vblank_event(dev, e, seq, &now); + } + spin_unlock_irqrestore(&dev->event_lock, irqflags); + + /* Will be reset by the modeset helpers when re-enabling the crtc by + * calling drm_calc_timestamping_constants(). */ + vblank->hwmode.crtc_clock = 0; +} +EXPORT_SYMBOL(drm_crtc_vblank_off); + +/** + * drm_crtc_vblank_reset - reset vblank state to off on a CRTC + * @crtc: CRTC in question + * + * Drivers can use this function to reset the vblank state to off at load time. + * Drivers should use this together with the drm_crtc_vblank_off() and + * drm_crtc_vblank_on() functions. The difference compared to + * drm_crtc_vblank_off() is that this function doesn't save the vblank counter + * and hence doesn't need to call any driver hooks. + */ +void drm_crtc_vblank_reset(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + unsigned long irqflags; + unsigned int pipe = drm_crtc_index(crtc); + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + + spin_lock_irqsave(&dev->vbl_lock, irqflags); + /* + * Prevent subsequent drm_vblank_get() from enabling the vblank + * interrupt by bumping the refcount. + */ + if (!vblank->inmodeset) { + atomic_inc(&vblank->refcount); + vblank->inmodeset = 1; + } + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); + + WARN_ON(!list_empty(&dev->vblank_event_list)); +} +EXPORT_SYMBOL(drm_crtc_vblank_reset); + +/** + * drm_crtc_vblank_on - enable vblank events on a CRTC + * @crtc: CRTC in question + * + * This functions restores the vblank interrupt state captured with + * drm_crtc_vblank_off() again. Note that calls to drm_crtc_vblank_on() and + * drm_crtc_vblank_off() can be unbalanced and so can also be unconditionally called + * in driver load code to reflect the current hardware state of the crtc. + */ +void drm_crtc_vblank_on(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + unsigned int pipe = drm_crtc_index(crtc); + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + unsigned long irqflags; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + + spin_lock_irqsave(&dev->vbl_lock, irqflags); + DRM_DEBUG_VBL("crtc %d, vblank enabled %d, inmodeset %d\n", + pipe, vblank->enabled, vblank->inmodeset); + + /* Drop our private "prevent drm_vblank_get" refcount */ + if (vblank->inmodeset) { + atomic_dec(&vblank->refcount); + vblank->inmodeset = 0; + } + + drm_reset_vblank_timestamp(dev, pipe); + + /* + * re-enable interrupts if there are users left, or the + * user wishes vblank interrupts to be enabled all the time. + */ + if (atomic_read(&vblank->refcount) != 0 || drm_vblank_offdelay == 0) + WARN_ON(drm_vblank_enable(dev, pipe)); + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); +} +EXPORT_SYMBOL(drm_crtc_vblank_on); + +static void drm_legacy_vblank_pre_modeset(struct drm_device *dev, + unsigned int pipe) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + + /* vblank is not initialized (IRQ not installed ?), or has been freed */ + if (!dev->num_crtcs) + return; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + + /* + * To avoid all the problems that might happen if interrupts + * were enabled/disabled around or between these calls, we just + * have the kernel take a reference on the CRTC (just once though + * to avoid corrupting the count if multiple, mismatch calls occur), + * so that interrupts remain enabled in the interim. + */ + if (!vblank->inmodeset) { + vblank->inmodeset = 0x1; + if (drm_vblank_get(dev, pipe) == 0) + vblank->inmodeset |= 0x2; + } +} + +static void drm_legacy_vblank_post_modeset(struct drm_device *dev, + unsigned int pipe) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + unsigned long irqflags; + + /* vblank is not initialized (IRQ not installed ?), or has been freed */ + if (!dev->num_crtcs) + return; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + + if (vblank->inmodeset) { + spin_lock_irqsave(&dev->vbl_lock, irqflags); + drm_reset_vblank_timestamp(dev, pipe); + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); + + if (vblank->inmodeset & 0x2) + drm_vblank_put(dev, pipe); + + vblank->inmodeset = 0; + } +} + +int drm_legacy_modeset_ctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_modeset_ctl *modeset = data; + unsigned int pipe; + + /* If drm_vblank_init() hasn't been called yet, just no-op */ + if (!dev->num_crtcs) + return 0; + + /* KMS drivers handle this internally */ + if (!drm_core_check_feature(dev, DRIVER_LEGACY)) + return 0; + + pipe = modeset->crtc; + if (pipe >= dev->num_crtcs) + return -EINVAL; + + switch (modeset->cmd) { + case _DRM_PRE_MODESET: + drm_legacy_vblank_pre_modeset(dev, pipe); + break; + case _DRM_POST_MODESET: + drm_legacy_vblank_post_modeset(dev, pipe); + break; + default: + return -EINVAL; + } + + return 0; +} + +static inline bool vblank_passed(u32 seq, u32 ref) +{ + return (seq - ref) <= (1 << 23); +} + +static int drm_queue_vblank_event(struct drm_device *dev, unsigned int pipe, + union drm_wait_vblank *vblwait, + struct drm_file *file_priv) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + struct drm_pending_vblank_event *e; + struct timeval now; + unsigned long flags; + unsigned int seq; + int ret; + + e = kzalloc(sizeof(*e), GFP_KERNEL); + if (e == NULL) { + ret = -ENOMEM; + goto err_put; + } + + e->pipe = pipe; + e->event.base.type = DRM_EVENT_VBLANK; + e->event.base.length = sizeof(e->event); + e->event.user_data = vblwait->request.signal; + + spin_lock_irqsave(&dev->event_lock, flags); + + /* + * drm_crtc_vblank_off() might have been called after we called + * drm_vblank_get(). drm_crtc_vblank_off() holds event_lock around the + * vblank disable, so no need for further locking. The reference from + * drm_vblank_get() protects against vblank disable from another source. + */ + if (!READ_ONCE(vblank->enabled)) { + ret = -EINVAL; + goto err_unlock; + } + + ret = drm_event_reserve_init_locked(dev, file_priv, &e->base, + &e->event.base); + + if (ret) + goto err_unlock; + + seq = drm_vblank_count_and_time(dev, pipe, &now); + + DRM_DEBUG("event on vblank count %u, current %u, crtc %u\n", + vblwait->request.sequence, seq, pipe); + + trace_drm_vblank_event_queued(file_priv, pipe, + vblwait->request.sequence); + + e->event.sequence = vblwait->request.sequence; + if (vblank_passed(seq, vblwait->request.sequence)) { + drm_vblank_put(dev, pipe); + send_vblank_event(dev, e, seq, &now); + vblwait->reply.sequence = seq; + } else { + /* drm_handle_vblank_events will call drm_vblank_put */ + list_add_tail(&e->base.link, &dev->vblank_event_list); + vblwait->reply.sequence = vblwait->request.sequence; + } + + spin_unlock_irqrestore(&dev->event_lock, flags); + + return 0; + +err_unlock: + spin_unlock_irqrestore(&dev->event_lock, flags); + kfree(e); +err_put: + drm_vblank_put(dev, pipe); + return ret; +} + +static bool drm_wait_vblank_is_query(union drm_wait_vblank *vblwait) +{ + if (vblwait->request.sequence) + return false; + + return _DRM_VBLANK_RELATIVE == + (vblwait->request.type & (_DRM_VBLANK_TYPES_MASK | + _DRM_VBLANK_EVENT | + _DRM_VBLANK_NEXTONMISS)); +} + +/* + * Wait for VBLANK. + * + * \param inode device inode. + * \param file_priv DRM file private. + * \param cmd command. + * \param data user argument, pointing to a drm_wait_vblank structure. + * \return zero on success or a negative number on failure. + * + * This function enables the vblank interrupt on the pipe requested, then + * sleeps waiting for the requested sequence number to occur, and drops + * the vblank interrupt refcount afterwards. (vblank IRQ disable follows that + * after a timeout with no further vblank waits scheduled). + */ +int drm_wait_vblank(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_vblank_crtc *vblank; + union drm_wait_vblank *vblwait = data; + int ret; + unsigned int flags, seq, pipe, high_pipe; + + if (!dev->irq_enabled) + return -EINVAL; + + if (vblwait->request.type & _DRM_VBLANK_SIGNAL) + return -EINVAL; + + if (vblwait->request.type & + ~(_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK | + _DRM_VBLANK_HIGH_CRTC_MASK)) { + DRM_ERROR("Unsupported type value 0x%x, supported mask 0x%x\n", + vblwait->request.type, + (_DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK | + _DRM_VBLANK_HIGH_CRTC_MASK)); + return -EINVAL; + } + + flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK; + high_pipe = (vblwait->request.type & _DRM_VBLANK_HIGH_CRTC_MASK); + if (high_pipe) + pipe = high_pipe >> _DRM_VBLANK_HIGH_CRTC_SHIFT; + else + pipe = flags & _DRM_VBLANK_SECONDARY ? 1 : 0; + if (pipe >= dev->num_crtcs) + return -EINVAL; + + vblank = &dev->vblank[pipe]; + + /* If the counter is currently enabled and accurate, short-circuit + * queries to return the cached timestamp of the last vblank. + */ + if (dev->vblank_disable_immediate && + drm_wait_vblank_is_query(vblwait) && + READ_ONCE(vblank->enabled)) { + struct timeval now; + + vblwait->reply.sequence = + drm_vblank_count_and_time(dev, pipe, &now); + vblwait->reply.tval_sec = now.tv_sec; + vblwait->reply.tval_usec = now.tv_usec; + return 0; + } + + ret = drm_vblank_get(dev, pipe); + if (ret) { + DRM_DEBUG("crtc %d failed to acquire vblank counter, %d\n", pipe, ret); + return ret; + } + seq = drm_vblank_count(dev, pipe); + + switch (vblwait->request.type & _DRM_VBLANK_TYPES_MASK) { + case _DRM_VBLANK_RELATIVE: + vblwait->request.sequence += seq; + vblwait->request.type &= ~_DRM_VBLANK_RELATIVE; + case _DRM_VBLANK_ABSOLUTE: + break; + default: + ret = -EINVAL; + goto done; + } + + if ((flags & _DRM_VBLANK_NEXTONMISS) && + vblank_passed(seq, vblwait->request.sequence)) + vblwait->request.sequence = seq + 1; + + if (flags & _DRM_VBLANK_EVENT) { + /* must hold on to the vblank ref until the event fires + * drm_vblank_put will be called asynchronously + */ + return drm_queue_vblank_event(dev, pipe, vblwait, file_priv); + } + + if (vblwait->request.sequence != seq) { + DRM_DEBUG("waiting on vblank count %u, crtc %u\n", + vblwait->request.sequence, pipe); + DRM_WAIT_ON(ret, vblank->queue, 3 * HZ, + vblank_passed(drm_vblank_count(dev, pipe), + vblwait->request.sequence) || + !READ_ONCE(vblank->enabled)); + } + + if (ret != -EINTR) { + struct timeval now; + + vblwait->reply.sequence = drm_vblank_count_and_time(dev, pipe, &now); + vblwait->reply.tval_sec = now.tv_sec; + vblwait->reply.tval_usec = now.tv_usec; + + DRM_DEBUG("crtc %d returning %u to client\n", + pipe, vblwait->reply.sequence); + } else { + DRM_DEBUG("crtc %d vblank wait interrupted by signal\n", pipe); + } + +done: + drm_vblank_put(dev, pipe); + return ret; +} + +static void drm_handle_vblank_events(struct drm_device *dev, unsigned int pipe) +{ + struct drm_pending_vblank_event *e, *t; + struct timeval now; + unsigned int seq; + + assert_spin_locked(&dev->event_lock); + + seq = drm_vblank_count_and_time(dev, pipe, &now); + + list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) { + if (e->pipe != pipe) + continue; + if (!vblank_passed(seq, e->event.sequence)) + continue; + + DRM_DEBUG("vblank event on %u, current %u\n", + e->event.sequence, seq); + + list_del(&e->base.link); + drm_vblank_put(dev, pipe); + send_vblank_event(dev, e, seq, &now); + } + + trace_drm_vblank_event(pipe, seq); +} + +/** + * drm_handle_vblank - handle a vblank event + * @dev: DRM device + * @pipe: index of CRTC where this event occurred + * + * Drivers should call this routine in their vblank interrupt handlers to + * update the vblank counter and send any signals that may be pending. + * + * This is the legacy version of drm_crtc_handle_vblank(). + */ +bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + unsigned long irqflags; + bool disable_irq; + + if (WARN_ON_ONCE(!dev->num_crtcs)) + return false; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return false; + + spin_lock_irqsave(&dev->event_lock, irqflags); + + /* Need timestamp lock to prevent concurrent execution with + * vblank enable/disable, as this would cause inconsistent + * or corrupted timestamps and vblank counts. + */ + spin_lock(&dev->vblank_time_lock); + + /* Vblank irq handling disabled. Nothing to do. */ + if (!vblank->enabled) { + spin_unlock(&dev->vblank_time_lock); + spin_unlock_irqrestore(&dev->event_lock, irqflags); + return false; + } + + drm_update_vblank_count(dev, pipe, true); + + spin_unlock(&dev->vblank_time_lock); + + wake_up(&vblank->queue); + + /* With instant-off, we defer disabling the interrupt until after + * we finish processing the following vblank after all events have + * been signaled. The disable has to be last (after + * drm_handle_vblank_events) so that the timestamp is always accurate. + */ + disable_irq = (dev->vblank_disable_immediate && + drm_vblank_offdelay > 0 && + !atomic_read(&vblank->refcount)); + + drm_handle_vblank_events(dev, pipe); + + spin_unlock_irqrestore(&dev->event_lock, irqflags); + + if (disable_irq) + vblank_disable_fn((unsigned long)vblank); + + return true; +} +EXPORT_SYMBOL(drm_handle_vblank); + +/** + * drm_crtc_handle_vblank - handle a vblank event + * @crtc: where this event occurred + * + * Drivers should call this routine in their vblank interrupt handlers to + * update the vblank counter and send any signals that may be pending. + * + * This is the native KMS version of drm_handle_vblank(). + * + * Returns: + * True if the event was successfully handled, false on failure. + */ +bool drm_crtc_handle_vblank(struct drm_crtc *crtc) +{ + return drm_handle_vblank(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_handle_vblank); diff --git a/include/drm/drmP.h b/include/drm/drmP.h index c363f2fdff31f..2e0b76cceb97e 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -80,6 +80,9 @@ #include #include #include +#include +#include + struct module; @@ -447,8 +450,6 @@ static inline bool drm_drv_uses_atomic_modeset(struct drm_device *dev) return dev->mode_config.funcs->atomic_commit != NULL; } -#include - #define DRM_SWITCH_POWER_ON 0 #define DRM_SWITCH_POWER_OFF 1 #define DRM_SWITCH_POWER_CHANGING 2 diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h index 5dd27ae5c47c0..d66f7ee07fb58 100644 --- a/include/drm/drm_file.h +++ b/include/drm/drm_file.h @@ -40,6 +40,7 @@ struct dma_fence; struct drm_file; struct drm_device; +struct device; /* * FIXME: Not sure we want to have drm_minor here in the end, but to avoid diff --git a/include/drm/drm_irq.h b/include/drm/drm_irq.h index 569ca86d4e1f0..d77f6e65b1c61 100644 --- a/include/drm/drm_irq.h +++ b/include/drm/drm_irq.h @@ -24,165 +24,9 @@ #ifndef _DRM_IRQ_H_ #define _DRM_IRQ_H_ -#include - -/** - * struct drm_pending_vblank_event - pending vblank event tracking - */ -struct drm_pending_vblank_event { - /** - * @base: Base structure for tracking pending DRM events. - */ - struct drm_pending_event base; - /** - * @pipe: drm_crtc_index() of the &drm_crtc this event is for. - */ - unsigned int pipe; - /** - * @event: Actual event which will be sent to userspace. - */ - struct drm_event_vblank event; -}; - -/** - * struct drm_vblank_crtc - vblank tracking for a CRTC - * - * This structure tracks the vblank state for one CRTC. - * - * Note that for historical reasons - the vblank handling code is still shared - * with legacy/non-kms drivers - this is a free-standing structure not directly - * connected to &struct drm_crtc. But all public interface functions are taking - * a &struct drm_crtc to hide this implementation detail. - */ -struct drm_vblank_crtc { - /** - * @dev: Pointer to the &drm_device. - */ - struct drm_device *dev; - /** - * @queue: Wait queue for vblank waiters. - */ - wait_queue_head_t queue; /**< VBLANK wait queue */ - /** - * @disable_timer: Disable timer for the delayed vblank disabling - * hysteresis logic. Vblank disabling is controlled through the - * drm_vblank_offdelay module option and the setting of the - * &drm_device.max_vblank_count value. - */ - struct timer_list disable_timer; - - /** - * @seqlock: Protect vblank count and time. - */ - seqlock_t seqlock; /* protects vblank count and time */ - - /** - * @count: Current software vblank counter. - */ - u32 count; - /** - * @time: Vblank timestamp corresponding to @count. - */ - struct timeval time; - - /** - * @refcount: Number of users/waiters of the vblank interrupt. Only when - * this refcount reaches 0 can the hardware interrupt be disabled using - * @disable_timer. - */ - atomic_t refcount; /* number of users of vblank interruptsper crtc */ - /** - * @last: Protected by &drm_device.vbl_lock, used for wraparound handling. - */ - u32 last; - /** - * @inmodeset: Tracks whether the vblank is disabled due to a modeset. - * For legacy driver bit 2 additionally tracks whether an additional - * temporary vblank reference has been acquired to paper over the - * hardware counter resetting/jumping. KMS drivers should instead just - * call drm_crtc_vblank_off() and drm_crtc_vblank_on(), which explicitly - * save and restore the vblank count. - */ - unsigned int inmodeset; /* Display driver is setting mode */ - /** - * @pipe: drm_crtc_index() of the &drm_crtc corresponding to this - * structure. - */ - unsigned int pipe; - /** - * @framedur_ns: Frame/Field duration in ns, used by - * drm_calc_vbltimestamp_from_scanoutpos() and computed by - * drm_calc_timestamping_constants(). - */ - int framedur_ns; - /** - * @linedur_ns: Line duration in ns, used by - * drm_calc_vbltimestamp_from_scanoutpos() and computed by - * drm_calc_timestamping_constants(). - */ - int linedur_ns; - - /** - * @hwmode: - * - * Cache of the current hardware display mode. Only valid when @enabled - * is set. This is used by helpers like - * drm_calc_vbltimestamp_from_scanoutpos(). We can't just access the - * hardware mode by e.g. looking at &drm_crtc_state.adjusted_mode, - * because that one is really hard to get from interrupt context. - */ - struct drm_display_mode hwmode; - - /** - * @enabled: Tracks the enabling state of the corresponding &drm_crtc to - * avoid double-disabling and hence corrupting saved state. Needed by - * drivers not using atomic KMS, since those might go through their CRTC - * disabling functions multiple times. - */ - bool enabled; -}; +struct drm_device; int drm_irq_install(struct drm_device *dev, int irq); int drm_irq_uninstall(struct drm_device *dev); -int drm_vblank_init(struct drm_device *dev, unsigned int num_crtcs); -u32 drm_crtc_vblank_count(struct drm_crtc *crtc); -u32 drm_crtc_vblank_count_and_time(struct drm_crtc *crtc, - struct timeval *vblanktime); -void drm_crtc_send_vblank_event(struct drm_crtc *crtc, - struct drm_pending_vblank_event *e); -void drm_crtc_arm_vblank_event(struct drm_crtc *crtc, - struct drm_pending_vblank_event *e); -bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe); -bool drm_crtc_handle_vblank(struct drm_crtc *crtc); -int drm_crtc_vblank_get(struct drm_crtc *crtc); -void drm_crtc_vblank_put(struct drm_crtc *crtc); -void drm_wait_one_vblank(struct drm_device *dev, unsigned int pipe); -void drm_crtc_wait_one_vblank(struct drm_crtc *crtc); -void drm_crtc_vblank_off(struct drm_crtc *crtc); -void drm_crtc_vblank_reset(struct drm_crtc *crtc); -void drm_crtc_vblank_on(struct drm_crtc *crtc); -void drm_vblank_cleanup(struct drm_device *dev); -u32 drm_accurate_vblank_count(struct drm_crtc *crtc); - -bool drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, - unsigned int pipe, int *max_error, - struct timeval *vblank_time, - bool in_vblank_irq); -void drm_calc_timestamping_constants(struct drm_crtc *crtc, - const struct drm_display_mode *mode); - -/** - * drm_crtc_vblank_waitqueue - get vblank waitqueue for the CRTC - * @crtc: which CRTC's vblank waitqueue to retrieve - * - * This function returns a pointer to the vblank waitqueue for the CRTC. - * Drivers can use this to implement vblank waits using wait_event() and related - * functions. - */ -static inline wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc) -{ - return &crtc->dev->vblank[drm_crtc_index(crtc)].queue; -} - #endif diff --git a/include/drm/drm_prime.h b/include/drm/drm_prime.h index 59ccab402e85e..9cd9e36f77b53 100644 --- a/include/drm/drm_prime.h +++ b/include/drm/drm_prime.h @@ -59,6 +59,8 @@ struct drm_device; struct drm_gem_object; struct drm_file; +struct device; + struct dma_buf *drm_gem_prime_export(struct drm_device *dev, struct drm_gem_object *obj, int flags); diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h new file mode 100644 index 0000000000000..4cde47332dfad --- /dev/null +++ b/include/drm/drm_vblank.h @@ -0,0 +1,181 @@ +/* + * Copyright 2016 Intel Corp. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _DRM_VBLANK_H_ +#define _DRM_VBLANK_H_ + +#include +#include +#include + +#include +#include +#include + +struct drm_device; +struct drm_crtc; + +/** + * struct drm_pending_vblank_event - pending vblank event tracking + */ +struct drm_pending_vblank_event { + /** + * @base: Base structure for tracking pending DRM events. + */ + struct drm_pending_event base; + /** + * @pipe: drm_crtc_index() of the &drm_crtc this event is for. + */ + unsigned int pipe; + /** + * @event: Actual event which will be sent to userspace. + */ + struct drm_event_vblank event; +}; + +/** + * struct drm_vblank_crtc - vblank tracking for a CRTC + * + * This structure tracks the vblank state for one CRTC. + * + * Note that for historical reasons - the vblank handling code is still shared + * with legacy/non-kms drivers - this is a free-standing structure not directly + * connected to &struct drm_crtc. But all public interface functions are taking + * a &struct drm_crtc to hide this implementation detail. + */ +struct drm_vblank_crtc { + /** + * @dev: Pointer to the &drm_device. + */ + struct drm_device *dev; + /** + * @queue: Wait queue for vblank waiters. + */ + wait_queue_head_t queue; /**< VBLANK wait queue */ + /** + * @disable_timer: Disable timer for the delayed vblank disabling + * hysteresis logic. Vblank disabling is controlled through the + * drm_vblank_offdelay module option and the setting of the + * &drm_device.max_vblank_count value. + */ + struct timer_list disable_timer; + + /** + * @seqlock: Protect vblank count and time. + */ + seqlock_t seqlock; /* protects vblank count and time */ + + /** + * @count: Current software vblank counter. + */ + u32 count; + /** + * @time: Vblank timestamp corresponding to @count. + */ + struct timeval time; + + /** + * @refcount: Number of users/waiters of the vblank interrupt. Only when + * this refcount reaches 0 can the hardware interrupt be disabled using + * @disable_timer. + */ + atomic_t refcount; /* number of users of vblank interruptsper crtc */ + /** + * @last: Protected by &drm_device.vbl_lock, used for wraparound handling. + */ + u32 last; + /** + * @inmodeset: Tracks whether the vblank is disabled due to a modeset. + * For legacy driver bit 2 additionally tracks whether an additional + * temporary vblank reference has been acquired to paper over the + * hardware counter resetting/jumping. KMS drivers should instead just + * call drm_crtc_vblank_off() and drm_crtc_vblank_on(), which explicitly + * save and restore the vblank count. + */ + unsigned int inmodeset; /* Display driver is setting mode */ + /** + * @pipe: drm_crtc_index() of the &drm_crtc corresponding to this + * structure. + */ + unsigned int pipe; + /** + * @framedur_ns: Frame/Field duration in ns, used by + * drm_calc_vbltimestamp_from_scanoutpos() and computed by + * drm_calc_timestamping_constants(). + */ + int framedur_ns; + /** + * @linedur_ns: Line duration in ns, used by + * drm_calc_vbltimestamp_from_scanoutpos() and computed by + * drm_calc_timestamping_constants(). + */ + int linedur_ns; + + /** + * @hwmode: + * + * Cache of the current hardware display mode. Only valid when @enabled + * is set. This is used by helpers like + * drm_calc_vbltimestamp_from_scanoutpos(). We can't just access the + * hardware mode by e.g. looking at &drm_crtc_state.adjusted_mode, + * because that one is really hard to get from interrupt context. + */ + struct drm_display_mode hwmode; + + /** + * @enabled: Tracks the enabling state of the corresponding &drm_crtc to + * avoid double-disabling and hence corrupting saved state. Needed by + * drivers not using atomic KMS, since those might go through their CRTC + * disabling functions multiple times. + */ + bool enabled; +}; + +int drm_vblank_init(struct drm_device *dev, unsigned int num_crtcs); +u32 drm_crtc_vblank_count(struct drm_crtc *crtc); +u32 drm_crtc_vblank_count_and_time(struct drm_crtc *crtc, + struct timeval *vblanktime); +void drm_crtc_send_vblank_event(struct drm_crtc *crtc, + struct drm_pending_vblank_event *e); +void drm_crtc_arm_vblank_event(struct drm_crtc *crtc, + struct drm_pending_vblank_event *e); +bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe); +bool drm_crtc_handle_vblank(struct drm_crtc *crtc); +int drm_crtc_vblank_get(struct drm_crtc *crtc); +void drm_crtc_vblank_put(struct drm_crtc *crtc); +void drm_wait_one_vblank(struct drm_device *dev, unsigned int pipe); +void drm_crtc_wait_one_vblank(struct drm_crtc *crtc); +void drm_crtc_vblank_off(struct drm_crtc *crtc); +void drm_crtc_vblank_reset(struct drm_crtc *crtc); +void drm_crtc_vblank_on(struct drm_crtc *crtc); +void drm_vblank_cleanup(struct drm_device *dev); +u32 drm_accurate_vblank_count(struct drm_crtc *crtc); + +bool drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, + unsigned int pipe, int *max_error, + struct timeval *vblank_time, + bool in_vblank_irq); +void drm_calc_timestamping_constants(struct drm_crtc *crtc, + const struct drm_display_mode *mode); +wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc); +#endif From 16584b204573ece64de80f20eb6202495aeb35c2 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 31 May 2017 11:22:53 +0200 Subject: [PATCH 29/34] drm/doc: Polish irq helper documentation Pull a (much shorter) overview into drm_irq.c, and instead put the callback documentation into in-line comments in drm_drv.h. v2: Move the include stanzas back to the split-up patch (Stefan). Cc: Stefan Agner Reviewed-by: Stefan Agner Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170531092253.12833-1-daniel.vetter@ffwll.ch --- Documentation/gpu/drm-internals.rst | 54 ----------------------------- drivers/gpu/drm/drm_irq.c | 30 +++++++++++++--- drivers/gpu/drm/drm_vblank.c | 3 ++ include/drm/drmP.h | 9 +++-- include/drm/drm_drv.h | 33 ++++++++++++++++-- 5 files changed, 66 insertions(+), 63 deletions(-) diff --git a/Documentation/gpu/drm-internals.rst b/Documentation/gpu/drm-internals.rst index 9067cd9586bd3..f6882ad0b3c39 100644 --- a/Documentation/gpu/drm-internals.rst +++ b/Documentation/gpu/drm-internals.rst @@ -149,60 +149,6 @@ Device Instance and Driver Handling Driver Load ----------- -IRQ Registration -~~~~~~~~~~~~~~~~ - -The DRM core tries to facilitate IRQ handler registration and -unregistration by providing :c:func:`drm_irq_install()` and -:c:func:`drm_irq_uninstall()` functions. Those functions only -support a single interrupt per device, devices that use more than one -IRQs need to be handled manually. - -Managed IRQ Registration -'''''''''''''''''''''''' - -:c:func:`drm_irq_install()` starts by calling the irq_preinstall -driver operation. The operation is optional and must make sure that the -interrupt will not get fired by clearing all pending interrupt flags or -disabling the interrupt. - -The passed-in IRQ will then be requested by a call to -:c:func:`request_irq()`. If the DRIVER_IRQ_SHARED driver feature -flag is set, a shared (IRQF_SHARED) IRQ handler will be requested. - -The IRQ handler function must be provided as the mandatory irq_handler -driver operation. It will get passed directly to -:c:func:`request_irq()` and thus has the same prototype as all IRQ -handlers. It will get called with a pointer to the DRM device as the -second argument. - -Finally the function calls the optional irq_postinstall driver -operation. The operation usually enables interrupts (excluding the -vblank interrupt, which is enabled separately), but drivers may choose -to enable/disable interrupts at a different time. - -:c:func:`drm_irq_uninstall()` is similarly used to uninstall an -IRQ handler. It starts by waking up all processes waiting on a vblank -interrupt to make sure they don't hang, and then calls the optional -irq_uninstall driver operation. The operation must disable all hardware -interrupts. Finally the function frees the IRQ by calling -:c:func:`free_irq()`. - -Manual IRQ Registration -''''''''''''''''''''''' - -Drivers that require multiple interrupt handlers can't use the managed -IRQ registration functions. In that case IRQs must be registered and -unregistered manually (usually with the :c:func:`request_irq()` and -:c:func:`free_irq()` functions, or their :c:func:`devm_request_irq()` and -:c:func:`devm_free_irq()` equivalents). - -When manually registering IRQs, drivers must not set the -DRIVER_HAVE_IRQ driver feature flag, and must not provide the -irq_handler driver operation. They must set the :c:type:`struct -drm_device ` irq_enabled field to 1 upon -registration of the IRQs, and clear it to 0 after unregistering the -IRQs. IRQ Helper Library ~~~~~~~~~~~~~~~~~~ diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 28d736c3fcb4f..3b04c25100aeb 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -61,20 +61,40 @@ #include "drm_internal.h" +/** + * DOC: irq helpers + * + * The DRM core provides very simple support helpers to enable IRQ handling on a + * device through the drm_irq_install() and drm_irq_uninstall() functions. This + * only supports devices with a single interrupt on the main device stored in + * &drm_device.dev and set as the device paramter in drm_dev_alloc(). + * + * These IRQ helpers are strictly optional. Drivers which roll their own only + * need to set &drm_device.irq_enabled to signal the DRM core that vblank + * interrupts are working. Since these helpers don't automatically clean up the + * requested interrupt like e.g. devm_request_irq() they're not really + * recommended. + */ + /** * drm_irq_install - install IRQ handler * @dev: DRM device * @irq: IRQ number to install the handler for * * Initializes the IRQ related data. Installs the handler, calling the driver - * irq_preinstall() and irq_postinstall() functions before and after the - * installation. + * &drm_driver.irq_preinstall and &drm_driver.irq_postinstall functions before + * and after the installation. * * This is the simplified helper interface provided for drivers with no special * needs. Drivers which need to install interrupt handlers for multiple * interrupts must instead set &drm_device.irq_enabled to signal the DRM core * that vblank interrupts are available. * + * @irq must match the interrupt number that would be passed to request_irq(), + * if called directly instead of using this helper function. + * + * &drm_driver.irq_handler is called to handle the registered interrupt. + * * Returns: * Zero on success or a negative error code on failure. */ @@ -136,9 +156,9 @@ EXPORT_SYMBOL(drm_irq_install); * drm_irq_uninstall - uninstall the IRQ handler * @dev: DRM device * - * Calls the driver's irq_uninstall() function and unregisters the IRQ handler. - * This should only be called by drivers which used drm_irq_install() to set up - * their interrupt handler. Other drivers must only reset + * Calls the driver's &drm_driver.irq_uninstall function and unregisters the IRQ + * handler. This should only be called by drivers which used drm_irq_install() + * to set up their interrupt handler. Other drivers must only reset * &drm_device.irq_enabled to false. * * Note that for kernel modesetting drivers it is a bug if this function fails. diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c index 630dc26379b70..463e4d81fb0dd 100644 --- a/drivers/gpu/drm/drm_vblank.c +++ b/drivers/gpu/drm/drm_vblank.c @@ -363,6 +363,9 @@ static void vblank_disable_fn(unsigned long arg) * @dev: DRM device * * This function cleans up any resources allocated in drm_vblank_init. + * + * Drivers which don't use drm_irq_install() need to set &drm_device.irq_enabled + * themselves, to signal to the DRM core that vblank interrupts are enabled. */ void drm_vblank_cleanup(struct drm_device *dev) { diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 2e0b76cceb97e..39df16af7a4a4 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -377,8 +377,13 @@ struct drm_device { int last_context; /**< Last current context */ /*@} */ - /** \name VBLANK IRQ support */ - /*@{ */ + /** + * @irq_enabled: + * + * Indicates that interrupt handling is enabled, specifically vblank + * handling. Drivers which don't use drm_irq_install() need to set this + * to true manually. + */ bool irq_enabled; int irq; diff --git a/include/drm/drm_drv.h b/include/drm/drm_drv.h index e64e33b9dd26b..18f3181674e8a 100644 --- a/include/drm/drm_drv.h +++ b/include/drm/drm_drv.h @@ -327,11 +327,40 @@ struct drm_driver { struct timeval *vblank_time, bool in_vblank_irq); - /* these have to be filled in */ - + /** + * @irq_handler: + * + * Interrupt handler called when using drm_irq_install(). Not used by + * drivers which implement their own interrupt handling. + */ irqreturn_t(*irq_handler) (int irq, void *arg); + + /** + * @irq_preinstall: + * + * Optional callback used by drm_irq_install() which is called before + * the interrupt handler is registered. This should be used to clear out + * any pending interrupts (from e.g. firmware based drives) and reset + * the interrupt handling registers. + */ void (*irq_preinstall) (struct drm_device *dev); + + /** + * @irq_postinstall: + * + * Optional callback used by drm_irq_install() which is called after + * the interrupt handler is registered. This should be used to enable + * interrupt generation in the hardware. + */ int (*irq_postinstall) (struct drm_device *dev); + + /** + * @irq_uninstall: + * + * Optional callback used by drm_irq_uninstall() which is called before + * the interrupt handler is unregistered. This should be used to disable + * interrupt generation in the hardware. + */ void (*irq_uninstall) (struct drm_device *dev); /** From 21774f21dc673043c777a56c5dd0ea4e88c256d3 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 24 May 2017 16:51:51 +0200 Subject: [PATCH 30/34] drm/hdlcd|mali: Drop drm_vblank_cleanup IRQs are properly shut down, so it almost works as race-free shutdown. Except the irq is stopped after the vblank stuff, so boom anyway. Proper way would be to call drm_atomic_helper_shutdown before any of the kms things gets stopped. So no harm in removing the drm_vblank_cleanup here really. Same story for both hdlcd and mali. v2: Move misplaced malidp hunk to this patch (Liviu). Cc: Liviu Dudau Cc: Brian Starkey Acked-by: Liviu Dudau Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-17-daniel.vetter@ffwll.ch --- drivers/gpu/drm/arm/hdlcd_drv.c | 2 -- drivers/gpu/drm/arm/malidp_drv.c | 2 -- 2 files changed, 4 deletions(-) diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c index 0f49c4b127728..345c8357b2736 100644 --- a/drivers/gpu/drm/arm/hdlcd_drv.c +++ b/drivers/gpu/drm/arm/hdlcd_drv.c @@ -340,7 +340,6 @@ static int hdlcd_drm_bind(struct device *dev) } err_fbdev: drm_kms_helper_poll_fini(drm); - drm_vblank_cleanup(drm); err_vblank: pm_runtime_disable(drm->dev); err_pm_active: @@ -368,7 +367,6 @@ static void hdlcd_drm_unbind(struct device *dev) } drm_kms_helper_poll_fini(drm); component_unbind_all(dev, drm); - drm_vblank_cleanup(drm); pm_runtime_get_sync(drm->dev); drm_irq_uninstall(drm); pm_runtime_put_sync(drm->dev); diff --git a/drivers/gpu/drm/arm/malidp_drv.c b/drivers/gpu/drm/arm/malidp_drv.c index 0d3eb537d08b6..01b13d2199179 100644 --- a/drivers/gpu/drm/arm/malidp_drv.c +++ b/drivers/gpu/drm/arm/malidp_drv.c @@ -652,7 +652,6 @@ static int malidp_bind(struct device *dev) drm_kms_helper_poll_fini(drm); fbdev_fail: pm_runtime_get_sync(dev); - drm_vblank_cleanup(drm); vblank_fail: malidp_se_irq_fini(drm); malidp_de_irq_fini(drm); @@ -692,7 +691,6 @@ static void malidp_unbind(struct device *dev) } drm_kms_helper_poll_fini(drm); pm_runtime_get_sync(dev); - drm_vblank_cleanup(drm); malidp_se_irq_fini(drm); malidp_de_irq_fini(drm); component_unbind_all(dev, drm); From 991dca01dd6719d1dd1bd4c39f5a12680c74b15e Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Wed, 24 May 2017 16:51:53 +0200 Subject: [PATCH 31/34] drm/exynos: Drop drm_vblank_cleanup Only in the load failure path, where the hardware is quiet anyway. Cc: Inki Dae Cc: Joonyoung Shim Cc: Seung-Woo Kim Cc: Kyungmin Park Reviewed-by: Inki Dae Signed-off-by: Daniel Vetter Link: http://patchwork.freedesktop.org/patch/msgid/20170524145212.27837-19-daniel.vetter@ffwll.ch --- drivers/gpu/drm/exynos/exynos_drm_drv.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 99f0e67694d21..35a8dfc93836d 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -378,7 +378,7 @@ static int exynos_drm_bind(struct device *dev) /* Probe non kms sub drivers and virtual display driver. */ ret = exynos_drm_device_subdrv_probe(drm); if (ret) - goto err_cleanup_vblank; + goto err_unbind_all; drm_mode_config_reset(drm); @@ -409,8 +409,6 @@ static int exynos_drm_bind(struct device *dev) exynos_drm_fbdev_fini(drm); drm_kms_helper_poll_fini(drm); exynos_drm_device_subdrv_remove(drm); -err_cleanup_vblank: - drm_vblank_cleanup(drm); err_unbind_all: component_unbind_all(drm->dev, drm); err_mode_config_cleanup: From a57bf53e294d2919f2bd1e0817c63105adf3c023 Mon Sep 17 00:00:00 2001 From: Jose Abreu Date: Thu, 25 May 2017 15:19:21 +0100 Subject: [PATCH 32/34] drm/atmel-hlcdc: Use crtc->mode_valid() callback Now that we have a callback to check if crtc supports a given mode we can use it in atmel-hlcdc so that we restrict the number of probbed modes to the ones we can actually display. Also, remove the mode_fixup() callback as this is no longer needed because mode_valid() will be called before. Signed-off-by: Jose Abreu Signed-off-by: Boris Brezillon Link: http://patchwork.freedesktop.org/patch/msgid/95fd6c06c58bd0b957e36a8d7068e6a74b581304.1495720737.git.joabreu@synopsys.com --- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c | 10 +++++----- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 5 +++-- drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c index 53bfa56ca47aa..53489859997b4 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c @@ -140,13 +140,13 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c) cfg); } -static bool atmel_hlcdc_crtc_mode_fixup(struct drm_crtc *c, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) +static enum drm_mode_status +atmel_hlcdc_crtc_mode_valid(struct drm_crtc *c, + const struct drm_display_mode *mode) { struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); - return atmel_hlcdc_dc_mode_valid(crtc->dc, adjusted_mode) == MODE_OK; + return atmel_hlcdc_dc_mode_valid(crtc->dc, mode); } static void atmel_hlcdc_crtc_disable(struct drm_crtc *c) @@ -315,7 +315,7 @@ static void atmel_hlcdc_crtc_atomic_flush(struct drm_crtc *crtc, } static const struct drm_crtc_helper_funcs lcdc_crtc_helper_funcs = { - .mode_fixup = atmel_hlcdc_crtc_mode_fixup, + .mode_valid = atmel_hlcdc_crtc_mode_valid, .mode_set = drm_helper_crtc_mode_set, .mode_set_nofb = atmel_hlcdc_crtc_mode_set_nofb, .mode_set_base = drm_helper_crtc_mode_set_base, diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c index 288dd4b701262..30dbffdb45a35 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c @@ -375,8 +375,9 @@ static const struct of_device_id atmel_hlcdc_of_match[] = { }; MODULE_DEVICE_TABLE(of, atmel_hlcdc_of_match); -int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc, - struct drm_display_mode *mode) +enum drm_mode_status +atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc, + const struct drm_display_mode *mode) { int vfront_porch = mode->vsync_start - mode->vdisplay; int vback_porch = mode->vtotal - mode->vsync_end; diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h index 433641b6e23b5..b0596a84c1b88 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h @@ -422,8 +422,9 @@ static inline void atmel_hlcdc_layer_init(struct atmel_hlcdc_layer *layer, layer->regmap = regmap; } -int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc, - struct drm_display_mode *mode); +enum drm_mode_status +atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc, + const struct drm_display_mode *mode); int atmel_hlcdc_create_planes(struct drm_device *dev); void atmel_hlcdc_plane_irq(struct atmel_hlcdc_plane *plane); From 71ae3df2445a6cb3a1c1980954ee79ac944165f8 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Fri, 2 Jun 2017 13:25:10 +0200 Subject: [PATCH 33/34] drm: remove writeq/readq function definitions Instead of rewriting write/readq, use linux/io-64-nonatomic-lo-hi.h which already have them. Signed-off-by: Corentin Labbe Signed-off-by: Sean Paul Link: http://patchwork.freedesktop.org/patch/msgid/20170602112510.17544-1-clabbe.montjoie@gmail.com --- include/drm/drm_os_linux.h | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/include/drm/drm_os_linux.h b/include/drm/drm_os_linux.h index 35e1482ba8a15..10122353b7448 100644 --- a/include/drm/drm_os_linux.h +++ b/include/drm/drm_os_linux.h @@ -6,19 +6,7 @@ #include /* For task queue support */ #include #include - -#ifndef readq -static inline u64 readq(void __iomem *reg) -{ - return ((u64) readl(reg)) | (((u64) readl(reg + 4UL)) << 32); -} - -static inline void writeq(u64 val, void __iomem *reg) -{ - writel(val & 0xffffffff, reg); - writel(val >> 32, reg + 0x4UL); -} -#endif +#include /** Current process ID */ #define DRM_CURRENTPID task_pid_nr(current) From 7f696942a7e52df2a99410bf23da513f9aad04fb Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Mon, 15 May 2017 10:16:15 -0700 Subject: [PATCH 34/34] drm/vc4: Mark the device as active when enabling runtime PM. Failing to do so meant that we got a resume() callback on first use of the device, so we would leak the bin BO that we allocated during probe. Signed-off-by: Eric Anholt Fixes: 553c942f8b2c ("drm/vc4: Allow using more than 256MB of CMA memory.") Link: http://patchwork.freedesktop.org/patch/msgid/20170515171615.10168-1-eric@anholt.net Reviewed-by: Boris Brezillon --- drivers/gpu/drm/vc4/vc4_v3d.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index 5ae5518e605ba..8c723da71f66c 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -401,6 +401,7 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) return ret; } + pm_runtime_set_active(dev); pm_runtime_use_autosuspend(dev); pm_runtime_set_autosuspend_delay(dev, 40); /* a little over 2 frames. */ pm_runtime_enable(dev);