diff options
author | Dave Airlie <airlied@redhat.com> | 2016-09-28 11:22:27 +1000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2016-09-28 11:22:27 +1000 |
commit | 378db830c3cc5c05e2c176274b0d2fcee0b133f3 (patch) | |
tree | 74664de990ece01ba3360973e8e3adc3debb2436 | |
parent | 662d5c957161802e2f26317bfe02108fdb215706 (diff) | |
parent | 8c763c9b1072c91aac90e5bc0a2ac2220a6980d7 (diff) | |
download | linux-378db830c3cc5c05e2c176274b0d2fcee0b133f3.tar.bz2 |
Merge branch 'for-next' of ssh://people.freedesktop.org/~seanpaul/dogwood into drm-next
I've included some improvements to PSR from myself, as well as a great
series from Tomasz to clean up and tighten up vblank/flip handling.
The last patch is one from Tomeu that has been floating around for a
while, and since rockchip is one of the beneficiaries, I figured this
would be a good place to pick it up.
* 'for-next' of ssh://people.freedesktop.org/~seanpaul/dogwood:
drm/rockchip: Balance irq refcount on failure
drm/rockchip: Kill vop_plane_state
drm/rockchip: Always signal event in next vblank after cfg_done
drm/rockchip: Do not enable vblank without event
drm/rockchip: Replace custom wait_for_vblanks with helper
drm/rockchip: Unreference framebuffers from flip work
drm/rockchip: Avoid race with vblank count increment
drm/rockchip: Get rid of some unnecessary code
drm/rockchip: Clear interrupt status bits before enabling
drm/rockchip: Fix up bug in psr state machine
drm/bridge: analogix_dp: Remove duplicated code
drm/rockchip: Reduce psr flush time to 100ms
drm/rockchip: Don't key off vblank for psr
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 66 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_psr.c | 80 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_psr.h | 8 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 259 |
5 files changed, 165 insertions, 249 deletions
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 5c698456aa1c..fb6226cf84b7 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -39,7 +39,6 @@ struct drm_connector; struct rockchip_crtc_funcs { int (*enable_vblank)(struct drm_crtc *crtc); void (*disable_vblank)(struct drm_crtc *crtc); - void (*wait_for_update)(struct drm_crtc *crtc); }; struct rockchip_crtc_state { diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c index 60bcc48f84b9..0f6eda023bd0 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c @@ -70,7 +70,7 @@ static int rockchip_drm_fb_dirty(struct drm_framebuffer *fb, struct drm_clip_rect *clips, unsigned int num_clips) { - rockchip_drm_psr_flush(fb->dev); + rockchip_drm_psr_flush_all(fb->dev); return 0; } @@ -174,68 +174,6 @@ static void rockchip_drm_output_poll_changed(struct drm_device *dev) drm_fb_helper_hotplug_event(fb_helper); } -static void rockchip_crtc_wait_for_update(struct drm_crtc *crtc) -{ - struct rockchip_drm_private *priv = crtc->dev->dev_private; - int pipe = drm_crtc_index(crtc); - const struct rockchip_crtc_funcs *crtc_funcs = priv->crtc_funcs[pipe]; - - if (crtc_funcs && crtc_funcs->wait_for_update) - crtc_funcs->wait_for_update(crtc); -} - -/* - * We can't use drm_atomic_helper_wait_for_vblanks() because rk3288 and rk3066 - * have hardware counters for neither vblanks nor scanlines, which results in - * a race where: - * | <-- HW vsync irq and reg take effect - * plane_commit --> | - * get_vblank and wait --> | - * | <-- handle_vblank, vblank->count + 1 - * cleanup_fb --> | - * iommu crash --> | - * | <-- HW vsync irq and reg take effect - * - * This function is equivalent but uses rockchip_crtc_wait_for_update() instead - * of waiting for vblank_count to change. - */ -static void -rockchip_atomic_wait_for_complete(struct drm_device *dev, struct drm_atomic_state *old_state) -{ - struct drm_crtc_state *old_crtc_state; - struct drm_crtc *crtc; - int i, ret; - - for_each_crtc_in_state(old_state, crtc, old_crtc_state, i) { - /* No one cares about the old state, so abuse it for tracking - * and store whether we hold a vblank reference (and should do a - * vblank wait) in the ->enable boolean. - */ - old_crtc_state->enable = false; - - if (!crtc->state->active) - continue; - - if (!drm_atomic_helper_framebuffer_changed(dev, - old_state, crtc)) - continue; - - ret = drm_crtc_vblank_get(crtc); - if (ret != 0) - continue; - - old_crtc_state->enable = true; - } - - for_each_crtc_in_state(old_state, crtc, old_crtc_state, i) { - if (!old_crtc_state->enable) - continue; - - rockchip_crtc_wait_for_update(crtc); - drm_crtc_vblank_put(crtc); - } -} - static void rockchip_atomic_commit_tail(struct drm_atomic_state *state) { @@ -250,7 +188,7 @@ rockchip_atomic_commit_tail(struct drm_atomic_state *state) drm_atomic_helper_commit_hw_done(state); - rockchip_atomic_wait_for_complete(dev, state); + drm_atomic_helper_wait_for_vblanks(dev, state); drm_atomic_helper_cleanup_planes(dev, state); } diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c index c6ac5d01c8e4..a553e182ff53 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c @@ -18,7 +18,7 @@ #include "rockchip_drm_drv.h" #include "rockchip_drm_psr.h" -#define PSR_FLUSH_TIMEOUT msecs_to_jiffies(3000) /* 3 seconds */ +#define PSR_FLUSH_TIMEOUT msecs_to_jiffies(100) enum psr_state { PSR_FLUSH, @@ -31,6 +31,7 @@ struct psr_drv { struct drm_encoder *encoder; spinlock_t lock; + bool active; enum psr_state state; struct timer_list flush_timer; @@ -67,19 +68,17 @@ static void psr_set_state_locked(struct psr_drv *psr, enum psr_state state) * v | | * PSR_DISABLE < - - - - - - - - - */ - if (state == psr->state) + if (state == psr->state || !psr->active) return; - /* Requesting a flush when disabled is a noop */ - if (state == PSR_FLUSH && psr->state == PSR_DISABLE) + /* Already disabled in flush, change the state, but not the hardware */ + if (state == PSR_DISABLE && psr->state == PSR_FLUSH) { + psr->state = state; return; + } psr->state = state; - /* Already disabled in flush, change the state, but not the hardware */ - if (state == PSR_DISABLE && psr->state == PSR_FLUSH) - return; - /* Actually commit the state change to hardware */ switch (psr->state) { case PSR_ENABLE: @@ -115,45 +114,79 @@ static void psr_flush_handler(unsigned long data) } /** - * rockchip_drm_psr_enable - enable the encoder PSR which bind to given CRTC + * rockchip_drm_psr_activate - activate PSR on the given pipe * @crtc: CRTC to obtain the PSR encoder * * Returns: * Zero on success, negative errno on failure. */ -int rockchip_drm_psr_enable(struct drm_crtc *crtc) +int rockchip_drm_psr_activate(struct drm_crtc *crtc) { struct psr_drv *psr = find_psr_by_crtc(crtc); + unsigned long flags; if (IS_ERR(psr)) return PTR_ERR(psr); - psr_set_state(psr, PSR_ENABLE); + spin_lock_irqsave(&psr->lock, flags); + psr->active = true; + spin_unlock_irqrestore(&psr->lock, flags); + return 0; } -EXPORT_SYMBOL(rockchip_drm_psr_enable); +EXPORT_SYMBOL(rockchip_drm_psr_activate); /** - * rockchip_drm_psr_disable - disable the encoder PSR which bind to given CRTC + * rockchip_drm_psr_deactivate - deactivate PSR on the given pipe * @crtc: CRTC to obtain the PSR encoder * * Returns: * Zero on success, negative errno on failure. */ -int rockchip_drm_psr_disable(struct drm_crtc *crtc) +int rockchip_drm_psr_deactivate(struct drm_crtc *crtc) { struct psr_drv *psr = find_psr_by_crtc(crtc); + unsigned long flags; if (IS_ERR(psr)) return PTR_ERR(psr); - psr_set_state(psr, PSR_DISABLE); + spin_lock_irqsave(&psr->lock, flags); + psr->active = false; + spin_unlock_irqrestore(&psr->lock, flags); + del_timer_sync(&psr->flush_timer); + return 0; } -EXPORT_SYMBOL(rockchip_drm_psr_disable); +EXPORT_SYMBOL(rockchip_drm_psr_deactivate); + +static void rockchip_drm_do_flush(struct psr_drv *psr) +{ + mod_timer(&psr->flush_timer, + round_jiffies_up(jiffies + PSR_FLUSH_TIMEOUT)); + psr_set_state(psr, PSR_FLUSH); +} /** - * rockchip_drm_psr_flush - force to flush all registered PSR encoders + * rockchip_drm_psr_flush - flush a single pipe + * @crtc: CRTC of the pipe to flush + * + * Returns: + * 0 on success, -errno on fail + */ +int rockchip_drm_psr_flush(struct drm_crtc *crtc) +{ + struct psr_drv *psr = find_psr_by_crtc(crtc); + if (IS_ERR(psr)) + return PTR_ERR(psr); + + rockchip_drm_do_flush(psr); + return 0; +} +EXPORT_SYMBOL(rockchip_drm_psr_flush); + +/** + * rockchip_drm_psr_flush_all - force to flush all registered PSR encoders * @dev: drm device * * Disable the PSR function for all registered encoders, and then enable the @@ -164,22 +197,18 @@ EXPORT_SYMBOL(rockchip_drm_psr_disable); * Returns: * Zero on success, negative errno on failure. */ -void rockchip_drm_psr_flush(struct drm_device *dev) +void rockchip_drm_psr_flush_all(struct drm_device *dev) { struct rockchip_drm_private *drm_drv = dev->dev_private; struct psr_drv *psr; unsigned long flags; spin_lock_irqsave(&drm_drv->psr_list_lock, flags); - list_for_each_entry(psr, &drm_drv->psr_list, list) { - mod_timer(&psr->flush_timer, - round_jiffies_up(jiffies + PSR_FLUSH_TIMEOUT)); - - psr_set_state(psr, PSR_FLUSH); - } + list_for_each_entry(psr, &drm_drv->psr_list, list) + rockchip_drm_do_flush(psr); spin_unlock_irqrestore(&drm_drv->psr_list_lock, flags); } -EXPORT_SYMBOL(rockchip_drm_psr_flush); +EXPORT_SYMBOL(rockchip_drm_psr_flush_all); /** * rockchip_drm_psr_register - register encoder to psr driver @@ -206,6 +235,7 @@ int rockchip_drm_psr_register(struct drm_encoder *encoder, setup_timer(&psr->flush_timer, psr_flush_handler, (unsigned long)psr); spin_lock_init(&psr->lock); + psr->active = true; psr->state = PSR_DISABLE; psr->encoder = encoder; psr->set = psr_set; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h index c35b68873a30..b420cf1bf902 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h @@ -15,9 +15,11 @@ #ifndef __ROCKCHIP_DRM_PSR___ #define __ROCKCHIP_DRM_PSR___ -void rockchip_drm_psr_flush(struct drm_device *dev); -int rockchip_drm_psr_enable(struct drm_crtc *crtc); -int rockchip_drm_psr_disable(struct drm_crtc *crtc); +void rockchip_drm_psr_flush_all(struct drm_device *dev); +int rockchip_drm_psr_flush(struct drm_crtc *crtc); + +int rockchip_drm_psr_activate(struct drm_crtc *crtc); +int rockchip_drm_psr_deactivate(struct drm_crtc *crtc); int rockchip_drm_psr_register(struct drm_encoder *encoder, void (*psr_set)(struct drm_encoder *, bool enable)); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index d486049f9722..c7eba305c488 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -17,12 +17,14 @@ #include <drm/drm_atomic.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_flip_work.h> #include <drm/drm_plane_helper.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/clk.h> +#include <linux/iopoll.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/pm_runtime.h> @@ -86,23 +88,15 @@ #define to_vop(x) container_of(x, struct vop, crtc) #define to_vop_win(x) container_of(x, struct vop_win, base) -#define to_vop_plane_state(x) container_of(x, struct vop_plane_state, base) -struct vop_plane_state { - struct drm_plane_state base; - int format; - dma_addr_t yrgb_mst; - bool enable; +enum vop_pending { + VOP_PENDING_FB_UNREF, }; struct vop_win { struct drm_plane base; const struct vop_win_data *data; struct vop *vop; - - /* protected by dev->event_lock */ - bool enable; - dma_addr_t yrgb_mst; }; struct vop { @@ -110,17 +104,18 @@ struct vop { struct device *dev; struct drm_device *drm_dev; bool is_enabled; - bool vblank_active; /* mutex vsync_ work */ struct mutex vsync_mutex; bool vsync_work_pending; struct completion dsp_hold_completion; - struct completion wait_update_complete; /* protected by dev->event_lock */ struct drm_pending_vblank_event *event; + struct drm_flip_work fb_unref_work; + unsigned long pending; + struct completion line_flag_completion; const struct vop_data *data; @@ -414,6 +409,7 @@ static void vop_dsp_hold_valid_irq_enable(struct vop *vop) spin_lock_irqsave(&vop->irq_lock, flags); + VOP_INTR_SET_TYPE(vop, clear, DSP_HOLD_VALID_INTR, 1); VOP_INTR_SET_TYPE(vop, enable, DSP_HOLD_VALID_INTR, 1); spin_unlock_irqrestore(&vop->irq_lock, flags); @@ -479,6 +475,7 @@ static void vop_line_flag_irq_enable(struct vop *vop, int line_num) spin_lock_irqsave(&vop->irq_lock, flags); VOP_CTRL_SET(vop, line_flag_num[0], line_num); + VOP_INTR_SET_TYPE(vop, clear, LINE_FLAG_INTR, 1); VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 1); spin_unlock_irqrestore(&vop->irq_lock, flags); @@ -569,6 +566,8 @@ static void vop_crtc_disable(struct drm_crtc *crtc) WARN_ON(vop->event); + rockchip_drm_psr_deactivate(&vop->crtc); + /* * We need to make sure that all windows are disabled before we * disable that crtc. Otherwise we might try to scan from a destroyed @@ -633,22 +632,6 @@ static void vop_plane_destroy(struct drm_plane *plane) drm_plane_cleanup(plane); } -static int vop_plane_prepare_fb(struct drm_plane *plane, - struct drm_plane_state *new_state) -{ - if (plane->state->fb) - drm_framebuffer_reference(plane->state->fb); - - return 0; -} - -static void vop_plane_cleanup_fb(struct drm_plane *plane, - struct drm_plane_state *old_state) -{ - if (old_state->fb) - drm_framebuffer_unreference(old_state->fb); -} - static int vop_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) { @@ -656,7 +639,6 @@ static int vop_plane_atomic_check(struct drm_plane *plane, struct drm_crtc_state *crtc_state; struct drm_framebuffer *fb = state->fb; struct vop_win *vop_win = to_vop_win(plane); - struct vop_plane_state *vop_plane_state = to_vop_plane_state(state); const struct vop_win_data *win = vop_win->data; int ret; struct drm_rect clip; @@ -666,7 +648,7 @@ static int vop_plane_atomic_check(struct drm_plane *plane, DRM_PLANE_HELPER_NO_SCALING; if (!crtc || !fb) - goto out_disable; + return 0; crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc); if (WARN_ON(!crtc_state)) @@ -684,11 +666,11 @@ static int vop_plane_atomic_check(struct drm_plane *plane, return ret; if (!state->visible) - goto out_disable; + return 0; - vop_plane_state->format = vop_convert_format(fb->pixel_format); - if (vop_plane_state->format < 0) - return vop_plane_state->format; + ret = vop_convert_format(fb->pixel_format); + if (ret < 0) + return ret; /* * Src.x1 can be odd when do clip, but yuv plane start point @@ -697,19 +679,12 @@ static int vop_plane_atomic_check(struct drm_plane *plane, if (is_yuv_support(fb->pixel_format) && ((state->src.x1 >> 16) % 2)) return -EINVAL; - vop_plane_state->enable = true; - - return 0; - -out_disable: - vop_plane_state->enable = false; return 0; } static void vop_plane_atomic_disable(struct drm_plane *plane, struct drm_plane_state *old_state) { - struct vop_plane_state *vop_plane_state = to_vop_plane_state(old_state); struct vop_win *vop_win = to_vop_win(plane); const struct vop_win_data *win = vop_win->data; struct vop *vop = to_vop(old_state->crtc); @@ -717,18 +692,11 @@ static void vop_plane_atomic_disable(struct drm_plane *plane, if (!old_state->crtc) return; - spin_lock_irq(&plane->dev->event_lock); - vop_win->enable = false; - vop_win->yrgb_mst = 0; - spin_unlock_irq(&plane->dev->event_lock); - spin_lock(&vop->reg_lock); VOP_WIN_SET(vop, win, enable, 0); spin_unlock(&vop->reg_lock); - - vop_plane_state->enable = false; } static void vop_plane_atomic_update(struct drm_plane *plane, @@ -737,7 +705,6 @@ static void vop_plane_atomic_update(struct drm_plane *plane, struct drm_plane_state *state = plane->state; struct drm_crtc *crtc = state->crtc; struct vop_win *vop_win = to_vop_win(plane); - struct vop_plane_state *vop_plane_state = to_vop_plane_state(state); const struct vop_win_data *win = vop_win->data; struct vop *vop = to_vop(state->crtc); struct drm_framebuffer *fb = state->fb; @@ -752,6 +719,7 @@ static void vop_plane_atomic_update(struct drm_plane *plane, dma_addr_t dma_addr; uint32_t val; bool rb_swap; + int format; /* * can't update plane when vop is disabled. @@ -762,7 +730,7 @@ static void vop_plane_atomic_update(struct drm_plane *plane, if (WARN_ON(!vop->is_enabled)) return; - if (!vop_plane_state->enable) { + if (!state->visible) { vop_plane_atomic_disable(plane, old_state); return; } @@ -783,18 +751,15 @@ static void vop_plane_atomic_update(struct drm_plane *plane, offset = (src->x1 >> 16) * drm_format_plane_cpp(fb->pixel_format, 0); offset += (src->y1 >> 16) * fb->pitches[0]; - vop_plane_state->yrgb_mst = rk_obj->dma_addr + offset + fb->offsets[0]; + dma_addr = rk_obj->dma_addr + offset + fb->offsets[0]; - spin_lock_irq(&plane->dev->event_lock); - vop_win->enable = true; - vop_win->yrgb_mst = vop_plane_state->yrgb_mst; - spin_unlock_irq(&plane->dev->event_lock); + format = vop_convert_format(fb->pixel_format); spin_lock(&vop->reg_lock); - VOP_WIN_SET(vop, win, format, vop_plane_state->format); + VOP_WIN_SET(vop, win, format, format); VOP_WIN_SET(vop, win, yrgb_vir, fb->pitches[0] >> 2); - VOP_WIN_SET(vop, win, yrgb_mst, vop_plane_state->yrgb_mst); + VOP_WIN_SET(vop, win, yrgb_mst, dma_addr); if (is_yuv_support(fb->pixel_format)) { int hsub = drm_format_horz_chroma_subsampling(fb->pixel_format); int vsub = drm_format_vert_chroma_subsampling(fb->pixel_format); @@ -841,68 +806,18 @@ static void vop_plane_atomic_update(struct drm_plane *plane, } static const struct drm_plane_helper_funcs plane_helper_funcs = { - .prepare_fb = vop_plane_prepare_fb, - .cleanup_fb = vop_plane_cleanup_fb, .atomic_check = vop_plane_atomic_check, .atomic_update = vop_plane_atomic_update, .atomic_disable = vop_plane_atomic_disable, }; -static void vop_atomic_plane_reset(struct drm_plane *plane) -{ - struct vop_plane_state *vop_plane_state = - to_vop_plane_state(plane->state); - - if (plane->state && plane->state->fb) - drm_framebuffer_unreference(plane->state->fb); - - kfree(vop_plane_state); - vop_plane_state = kzalloc(sizeof(*vop_plane_state), GFP_KERNEL); - if (!vop_plane_state) - return; - - plane->state = &vop_plane_state->base; - plane->state->plane = plane; -} - -static struct drm_plane_state * -vop_atomic_plane_duplicate_state(struct drm_plane *plane) -{ - struct vop_plane_state *old_vop_plane_state; - struct vop_plane_state *vop_plane_state; - - if (WARN_ON(!plane->state)) - return NULL; - - old_vop_plane_state = to_vop_plane_state(plane->state); - vop_plane_state = kmemdup(old_vop_plane_state, - sizeof(*vop_plane_state), GFP_KERNEL); - if (!vop_plane_state) - return NULL; - - __drm_atomic_helper_plane_duplicate_state(plane, - &vop_plane_state->base); - - return &vop_plane_state->base; -} - -static void vop_atomic_plane_destroy_state(struct drm_plane *plane, - struct drm_plane_state *state) -{ - struct vop_plane_state *vop_state = to_vop_plane_state(state); - - __drm_atomic_helper_plane_destroy_state(state); - - kfree(vop_state); -} - static const struct drm_plane_funcs vop_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .destroy = vop_plane_destroy, - .reset = vop_atomic_plane_reset, - .atomic_duplicate_state = vop_atomic_plane_duplicate_state, - .atomic_destroy_state = vop_atomic_plane_destroy_state, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, }; static int vop_crtc_enable_vblank(struct drm_crtc *crtc) @@ -915,12 +830,11 @@ static int vop_crtc_enable_vblank(struct drm_crtc *crtc) spin_lock_irqsave(&vop->irq_lock, flags); + VOP_INTR_SET_TYPE(vop, clear, FS_INTR, 1); VOP_INTR_SET_TYPE(vop, enable, FS_INTR, 1); spin_unlock_irqrestore(&vop->irq_lock, flags); - rockchip_drm_psr_disable(&vop->crtc); - return 0; } @@ -937,22 +851,11 @@ static void vop_crtc_disable_vblank(struct drm_crtc *crtc) VOP_INTR_SET_TYPE(vop, enable, FS_INTR, 0); spin_unlock_irqrestore(&vop->irq_lock, flags); - - rockchip_drm_psr_enable(&vop->crtc); -} - -static void vop_crtc_wait_for_update(struct drm_crtc *crtc) -{ - struct vop *vop = to_vop(crtc); - - reinit_completion(&vop->wait_update_complete); - WARN_ON(!wait_for_completion_timeout(&vop->wait_update_complete, 100)); } static const struct rockchip_crtc_funcs private_crtc_funcs = { .enable_vblank = vop_crtc_enable_vblank, .disable_vblank = vop_crtc_disable_vblank, - .wait_for_update = vop_crtc_wait_for_update, }; static bool vop_crtc_mode_fixup(struct drm_crtc *crtc, @@ -1072,12 +975,44 @@ static void vop_crtc_enable(struct drm_crtc *crtc) clk_set_rate(vop->dclk, adjusted_mode->clock * 1000); VOP_CTRL_SET(vop, standby, 0); + + rockchip_drm_psr_activate(&vop->crtc); +} + +static bool vop_fs_irq_is_pending(struct vop *vop) +{ + return VOP_INTR_GET_TYPE(vop, status, FS_INTR); +} + +static void vop_wait_for_irq_handler(struct vop *vop) +{ + bool pending; + int ret; + + /* + * Spin until frame start interrupt status bit goes low, which means + * that interrupt handler was invoked and cleared it. The timeout of + * 10 msecs is really too long, but it is just a safety measure if + * something goes really wrong. The wait will only happen in the very + * unlikely case of a vblank happening exactly at the same time and + * shouldn't exceed microseconds range. + */ + ret = readx_poll_timeout_atomic(vop_fs_irq_is_pending, vop, pending, + !pending, 0, 10 * 1000); + if (ret) + DRM_DEV_ERROR(vop->dev, "VOP vblank IRQ stuck for 10 ms\n"); + + synchronize_irq(vop->irq); } static void vop_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state) { + struct drm_atomic_state *old_state = old_crtc_state->state; + struct drm_plane_state *old_plane_state; struct vop *vop = to_vop(crtc); + struct drm_plane *plane; + int i; if (WARN_ON(!vop->is_enabled)) return; @@ -1087,23 +1022,42 @@ static void vop_crtc_atomic_flush(struct drm_crtc *crtc, vop_cfg_done(vop); spin_unlock(&vop->reg_lock); -} -static void vop_crtc_atomic_begin(struct drm_crtc *crtc, - struct drm_crtc_state *old_crtc_state) -{ - struct vop *vop = to_vop(crtc); + /* + * There is a (rather unlikely) possiblity that a vblank interrupt + * fired before we set the cfg_done bit. To avoid spuriously + * signalling flip completion we need to wait for it to finish. + */ + vop_wait_for_irq_handler(vop); spin_lock_irq(&crtc->dev->event_lock); - vop->vblank_active = true; - WARN_ON(drm_crtc_vblank_get(crtc) != 0); - WARN_ON(vop->event); - if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + WARN_ON(vop->event); + vop->event = crtc->state->event; crtc->state->event = NULL; } spin_unlock_irq(&crtc->dev->event_lock); + + for_each_plane_in_state(old_state, plane, old_plane_state, i) { + if (!old_plane_state->fb) + continue; + + if (old_plane_state->fb == plane->state->fb) + continue; + + drm_framebuffer_reference(old_plane_state->fb); + drm_flip_work_queue(&vop->fb_unref_work, old_plane_state->fb); + set_bit(VOP_PENDING_FB_UNREF, &vop->pending); + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + } +} + +static void vop_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + rockchip_drm_psr_flush(crtc); } static const struct drm_crtc_helper_funcs vop_crtc_helper_funcs = { @@ -1160,16 +1114,13 @@ static const struct drm_crtc_funcs vop_crtc_funcs = { .atomic_destroy_state = vop_crtc_destroy_state, }; -static bool vop_win_pending_is_complete(struct vop_win *vop_win) +static void vop_fb_unref_worker(struct drm_flip_work *work, void *val) { - dma_addr_t yrgb_mst; + struct vop *vop = container_of(work, struct vop, fb_unref_work); + struct drm_framebuffer *fb = val; - if (!vop_win->enable) - return VOP_WIN_GET(vop_win->vop, vop_win->data, enable) == 0; - - yrgb_mst = VOP_WIN_GET_YRGBADDR(vop_win->vop, vop_win->data); - - return yrgb_mst == vop_win->yrgb_mst; + drm_crtc_vblank_put(&vop->crtc); + drm_framebuffer_unreference(fb); } static void vop_handle_vblank(struct vop *vop) @@ -1177,27 +1128,17 @@ static void vop_handle_vblank(struct vop *vop) struct drm_device *drm = vop->drm_dev; struct drm_crtc *crtc = &vop->crtc; unsigned long flags; - int i; - - for (i = 0; i < vop->data->win_size; i++) { - if (!vop_win_pending_is_complete(&vop->win[i])) - return; - } spin_lock_irqsave(&drm->event_lock, flags); if (vop->event) { drm_crtc_send_vblank_event(crtc, vop->event); - vop->event = NULL; - - } - if (vop->vblank_active) { - vop->vblank_active = false; drm_crtc_vblank_put(crtc); + vop->event = NULL; } spin_unlock_irqrestore(&drm->event_lock, flags); - if (!completion_done(&vop->wait_update_complete)) - complete(&vop->wait_update_complete); + if (test_and_clear_bit(VOP_PENDING_FB_UNREF, &vop->pending)) + drm_flip_work_commit(&vop->fb_unref_work, system_unbound_wq); } static irqreturn_t vop_isr(int irq, void *data) @@ -1336,8 +1277,10 @@ static int vop_create_crtc(struct vop *vop) goto err_cleanup_crtc; } + drm_flip_work_init(&vop->fb_unref_work, "fb_unref", + vop_fb_unref_worker); + init_completion(&vop->dsp_hold_completion); - init_completion(&vop->wait_update_complete); init_completion(&vop->line_flag_completion); crtc->port = port; rockchip_register_crtc_funcs(crtc, &private_crtc_funcs); @@ -1379,6 +1322,7 @@ static void vop_destroy_crtc(struct vop *vop) * references the CRTC. */ drm_crtc_cleanup(crtc); + drm_flip_work_cleanup(&vop->fb_unref_work); } static int vop_initial(struct vop *vop) @@ -1466,7 +1410,6 @@ static int vop_initial(struct vop *vop) clk_disable(vop->aclk); vop->is_enabled = false; - vop->vblank_active = false; return 0; @@ -1604,11 +1547,15 @@ static int vop_bind(struct device *dev, struct device *master, void *data) ret = vop_create_crtc(vop); if (ret) - return ret; + goto err_enable_irq; pm_runtime_enable(&pdev->dev); return 0; + +err_enable_irq: + enable_irq(vop->irq); /* To balance out the disable_irq above */ + return ret; } static void vop_unbind(struct device *dev, struct device *master, void *data) |