diff options
author | Dave Airlie <airlied@redhat.com> | 2018-01-05 02:43:46 +0300 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2018-01-05 02:51:43 +0300 |
commit | bcd21a4786eae9f661acf552463fb718b24ee5f4 (patch) | |
tree | 24fd61d7ea7e75209e04aed5b952105ef91fb7cf /drivers/gpu/drm/armada | |
parent | 066f9eb47d0ff9f5e7f8e8b0b2710c7557834fbb (diff) | |
parent | 27ab688f70b6f6b51da4bf56b1237f7beb64d847 (diff) | |
download | linux-bcd21a4786eae9f661acf552463fb718b24ee5f4.tar.xz |
Merge branch 'drm-armada-devel-4.15' of git://git.armlinux.org.uk/~rmk/linux-arm into drm-next
This series builds upon the set of fixes previously submitted to move
Armada DRM closer to atomic modeset. We're nowhere near yet, but this
series helps to get us closer by unifying some of the differences
between the primary and overlay planes.
New features added allows userspace to disable the primary plane if
overlay is full screen and there's nothing obscuring the colorkey -
this saves having to fetch an entire buffer containing nothing but
colorkey when displaying full screen video.
[airlied: fixup for atomic plane helper rename:
a01cb8ba3f6282934cff65e89ab36b18b14cbe27
Author: Ville Syrjälä <ville.syrjala@linux.intel.com>
Date: Wed Nov 1 22:16:19 2017 +0200
drm: Move drm_plane_helper_check_state() into drm_atomic_helper.c
]
* 'drm-armada-devel-4.15' of git://git.armlinux.org.uk/~rmk/linux-arm: (29 commits)
drm/armada: expand overlay trace entry
drm/armada: implement primary plane update
drm/armada: extract register generation from armada_drm_primary_set()
drm/armada: wait for previous work when moving overlay window
drm/armada: move overlay plane register update generation
drm/armada: re-organise overlay register update generation
drm/armada: disable planes at next blanking period
drm/armada: avoid work allocation
drm/armada: allow armada_drm_plane_work_queue() to silently fail
drm/armada: use drm_plane_helper_check_state()
drm/armada: only enable HSMOOTH if scaling horizontally
drm/armada: move writes of LCD_SPU_SRAM_PARA1 under lock
drm/armada: move regs into armada_plane_work
drm/armada: move event sending into armada_plane_work
drm/armada: move fb retirement into armada_plane_work
drm/armada: move overlay plane work out from under spinlock
drm/armada: clear plane enable bit when disabling
drm/armada: clean up armada_drm_crtc_plane_disable()
drm/armada: allow the primary plane to be disabled
drm/armada: wait and cancel any pending frame work at disable
...
Diffstat (limited to 'drivers/gpu/drm/armada')
-rw-r--r-- | drivers/gpu/drm/armada/armada_crtc.c | 434 | ||||
-rw-r--r-- | drivers/gpu/drm/armada/armada_crtc.h | 26 | ||||
-rw-r--r-- | drivers/gpu/drm/armada/armada_overlay.c | 302 | ||||
-rw-r--r-- | drivers/gpu/drm/armada/armada_trace.h | 24 |
4 files changed, 496 insertions, 290 deletions
diff --git a/drivers/gpu/drm/armada/armada_crtc.c b/drivers/gpu/drm/armada/armada_crtc.c index 2e065facdce7..e2adfbef7d6b 100644 --- a/drivers/gpu/drm/armada/armada_crtc.c +++ b/drivers/gpu/drm/armada/armada_crtc.c @@ -13,6 +13,7 @@ #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_plane_helper.h> +#include <drm/drm_atomic_helper.h> #include "armada_crtc.h" #include "armada_drm.h" #include "armada_fb.h" @@ -20,13 +21,6 @@ #include "armada_hw.h" #include "armada_trace.h" -struct armada_frame_work { - struct armada_plane_work work; - struct drm_pending_vblank_event *event; - struct armada_regs regs[4]; - struct drm_framebuffer *old_fb; -}; - enum csc_mode { CSC_AUTO = 0, CSC_YUV_CCIR601 = 1, @@ -168,16 +162,23 @@ static void armada_drm_crtc_update(struct armada_crtc *dcrtc) void armada_drm_plane_calc_addrs(u32 *addrs, struct drm_framebuffer *fb, int x, int y) { + const struct drm_format_info *format = fb->format; + unsigned int num_planes = format->num_planes; u32 addr = drm_fb_obj(fb)->dev_addr; - int num_planes = fb->format->num_planes; int i; if (num_planes > 3) num_planes = 3; - for (i = 0; i < num_planes; i++) + addrs[0] = addr + fb->offsets[0] + y * fb->pitches[0] + + x * format->cpp[0]; + + y /= format->vsub; + x /= format->hsub; + + for (i = 1; i < num_planes; i++) addrs[i] = addr + fb->offsets[i] + y * fb->pitches[i] + - x * fb->format->cpp[i]; + x * format->cpp[i]; for (; i < 3; i++) addrs[i] = 0; } @@ -209,6 +210,38 @@ static unsigned armada_drm_crtc_calc_fb(struct drm_framebuffer *fb, return i; } +static void armada_drm_plane_work_call(struct armada_crtc *dcrtc, + struct armada_plane_work *work, + void (*fn)(struct armada_crtc *, struct armada_plane_work *)) +{ + struct armada_plane *dplane = drm_to_armada_plane(work->plane); + struct drm_pending_vblank_event *event; + struct drm_framebuffer *fb; + + if (fn) + fn(dcrtc, work); + drm_crtc_vblank_put(&dcrtc->crtc); + + event = work->event; + fb = work->old_fb; + if (event || fb) { + struct drm_device *dev = dcrtc->crtc.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + if (event) + drm_crtc_send_vblank_event(&dcrtc->crtc, event); + if (fb) + __armada_drm_queue_unref_work(dev, fb); + spin_unlock_irqrestore(&dev->event_lock, flags); + } + + if (work->need_kfree) + kfree(work); + + wake_up(&dplane->frame_wait); +} + static void armada_drm_plane_work_run(struct armada_crtc *dcrtc, struct drm_plane *plane) { @@ -216,24 +249,19 @@ static void armada_drm_plane_work_run(struct armada_crtc *dcrtc, struct armada_plane_work *work = xchg(&dplane->work, NULL); /* Handle any pending frame work. */ - if (work) { - work->fn(dcrtc, dplane, work); - drm_crtc_vblank_put(&dcrtc->crtc); - } - - wake_up(&dplane->frame_wait); + if (work) + armada_drm_plane_work_call(dcrtc, work, work->fn); } int armada_drm_plane_work_queue(struct armada_crtc *dcrtc, - struct armada_plane *plane, struct armada_plane_work *work) + struct armada_plane_work *work) { + struct armada_plane *plane = drm_to_armada_plane(work->plane); int ret; ret = drm_crtc_vblank_get(&dcrtc->crtc); - if (ret) { - DRM_ERROR("failed to acquire vblank counter\n"); + if (ret) return ret; - } ret = cmpxchg(&plane->work, NULL, work) ? -EBUSY : 0; if (ret) @@ -247,51 +275,60 @@ int armada_drm_plane_work_wait(struct armada_plane *plane, long timeout) return wait_event_timeout(plane->frame_wait, !plane->work, timeout); } -struct armada_plane_work *armada_drm_plane_work_cancel( - struct armada_crtc *dcrtc, struct armada_plane *plane) +void armada_drm_plane_work_cancel(struct armada_crtc *dcrtc, + struct armada_plane *dplane) { - struct armada_plane_work *work = xchg(&plane->work, NULL); + struct armada_plane_work *work = xchg(&dplane->work, NULL); if (work) - drm_crtc_vblank_put(&dcrtc->crtc); - - return work; + armada_drm_plane_work_call(dcrtc, work, work->cancel); } -static int armada_drm_crtc_queue_frame_work(struct armada_crtc *dcrtc, - struct armada_frame_work *work) +static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc, + struct armada_plane_work *work) { - struct armada_plane *plane = drm_to_armada_plane(dcrtc->crtc.primary); + unsigned long flags; - return armada_drm_plane_work_queue(dcrtc, plane, &work->work); + spin_lock_irqsave(&dcrtc->irq_lock, flags); + armada_drm_crtc_update_regs(dcrtc, work->regs); + spin_unlock_irqrestore(&dcrtc->irq_lock, flags); } -static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc, - struct armada_plane *plane, struct armada_plane_work *work) +static void armada_drm_crtc_complete_disable_work(struct armada_crtc *dcrtc, + struct armada_plane_work *work) { - struct armada_frame_work *fwork = container_of(work, struct armada_frame_work, work); - struct drm_device *dev = dcrtc->crtc.dev; unsigned long flags; + if (dcrtc->plane == work->plane) + dcrtc->plane = NULL; + spin_lock_irqsave(&dcrtc->irq_lock, flags); - armada_drm_crtc_update_regs(dcrtc, fwork->regs); + armada_drm_crtc_update_regs(dcrtc, work->regs); spin_unlock_irqrestore(&dcrtc->irq_lock, flags); +} - if (fwork->event) { - spin_lock_irqsave(&dev->event_lock, flags); - drm_crtc_send_vblank_event(&dcrtc->crtc, fwork->event); - spin_unlock_irqrestore(&dev->event_lock, flags); - } +static struct armada_plane_work * +armada_drm_crtc_alloc_plane_work(struct drm_plane *plane) +{ + struct armada_plane_work *work; + int i = 0; + + work = kzalloc(sizeof(*work), GFP_KERNEL); + if (!work) + return NULL; - /* Finally, queue the process-half of the cleanup. */ - __armada_drm_queue_unref_work(dcrtc->crtc.dev, fwork->old_fb); - kfree(fwork); + work->plane = plane; + work->fn = armada_drm_crtc_complete_frame_work; + work->need_kfree = true; + armada_reg_queue_end(work->regs, i); + + return work; } static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc, struct drm_framebuffer *fb, bool force) { - struct armada_frame_work *work; + struct armada_plane_work *work; if (!fb) return; @@ -302,15 +339,11 @@ static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc, return; } - work = kmalloc(sizeof(*work), GFP_KERNEL); + work = armada_drm_crtc_alloc_plane_work(dcrtc->crtc.primary); if (work) { - int i = 0; - work->work.fn = armada_drm_crtc_complete_frame_work; - work->event = NULL; work->old_fb = fb; - armada_reg_queue_end(work->regs, i); - if (armada_drm_crtc_queue_frame_work(dcrtc, work) == 0) + if (armada_drm_plane_work_queue(dcrtc, work) == 0) return; kfree(work); @@ -373,8 +406,11 @@ static void armada_drm_crtc_prepare(struct drm_crtc *crtc) * the new mode parameters. */ plane = dcrtc->plane; - if (plane) + if (plane) { drm_plane_force_disable(plane); + WARN_ON(!armada_drm_plane_work_wait(drm_to_armada_plane(plane), + HZ)); + } } /* The mode_config.mutex will be held for this call */ @@ -440,11 +476,11 @@ static void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat) if (stat & VSYNC_IRQ) drm_crtc_handle_vblank(&dcrtc->crtc); - spin_lock(&dcrtc->irq_lock); ovl_plane = dcrtc->plane; if (ovl_plane) armada_drm_plane_work_run(dcrtc, ovl_plane); + spin_lock(&dcrtc->irq_lock); if (stat & GRA_FRAME_IRQ && dcrtc->interlaced) { int i = stat & GRA_FRAME_IRQ0 ? 0 : 1; uint32_t val; @@ -536,18 +572,14 @@ static uint32_t armada_drm_crtc_calculate_csc(struct armada_crtc *dcrtc) return val; } -static void armada_drm_primary_set(struct drm_crtc *crtc, - struct drm_plane *plane, int x, int y) +static void armada_drm_gra_plane_regs(struct armada_regs *regs, + struct drm_framebuffer *fb, struct armada_plane_state *state, + int x, int y, bool interlaced) { - struct armada_plane_state *state = &drm_to_armada_plane(plane)->state; - struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); - struct armada_regs regs[8]; - bool interlaced = dcrtc->interlaced; - unsigned i; + unsigned int i; u32 ctrl0; - i = armada_drm_crtc_calc_fb(plane->fb, x, y, regs, interlaced); - + i = armada_drm_crtc_calc_fb(fb, x, y, regs, interlaced); armada_reg_queue_set(regs, i, state->dst_yx, LCD_SPU_GRA_OVSA_HPXL_VLN); armada_reg_queue_set(regs, i, state->src_hw, LCD_SPU_GRA_HPXL_VLN); armada_reg_queue_set(regs, i, state->dst_hw, LCD_SPU_GZM_HPXL_VLN); @@ -559,9 +591,21 @@ static void armada_drm_primary_set(struct drm_crtc *crtc, armada_reg_queue_mod(regs, i, ctrl0, CFG_GRAFORMAT | CFG_GRA_MOD(CFG_SWAPRB | CFG_SWAPUV | CFG_SWAPYU | CFG_YUV2RGB) | - CFG_PALETTE_ENA | CFG_GRA_FTOGGLE, + CFG_PALETTE_ENA | CFG_GRA_FTOGGLE | + CFG_GRA_HSMOOTH | CFG_GRA_ENA, LCD_SPU_DMA_CTRL0); armada_reg_queue_end(regs, i); +} + +static void armada_drm_primary_set(struct drm_crtc *crtc, + struct drm_plane *plane, int x, int y) +{ + struct armada_plane_state *state = &drm_to_armada_plane(plane)->state; + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); + struct armada_regs regs[8]; + bool interlaced = dcrtc->interlaced; + + armada_drm_gra_plane_regs(regs, plane->fb, state, x, y, interlaced); armada_drm_crtc_update_regs(dcrtc, regs); } @@ -581,7 +625,7 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc, interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE); - val = CFG_GRA_ENA | CFG_GRA_HSMOOTH; + val = CFG_GRA_ENA; val |= CFG_GRA_FMT(drm_fb_to_armada_fb(dcrtc->crtc.primary->fb)->fmt); val |= CFG_GRA_MOD(drm_fb_to_armada_fb(dcrtc->crtc.primary->fb)->mod); @@ -633,8 +677,6 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc, /* Now compute the divider for real */ dcrtc->variant->compute_clock(dcrtc, adj, &sclk); - /* Ensure graphic fifo is enabled */ - armada_reg_queue_mod(regs, i, 0, CFG_PDWN64x66, LCD_SPU_SRAM_PARA1); armada_reg_queue_set(regs, i, sclk, LCD_CFG_SCLK_DIV); if (interlaced ^ dcrtc->interlaced) { @@ -647,6 +689,9 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc, spin_lock_irqsave(&dcrtc->irq_lock, flags); + /* Ensure graphic fifo is enabled */ + armada_reg_queue_mod(regs, i, 0, CFG_PDWN64x66, LCD_SPU_SRAM_PARA1); + /* Even interlaced/progressive frame */ dcrtc->v[1].spu_v_h_total = adj->crtc_vtotal << 16 | adj->crtc_htotal; @@ -729,47 +774,13 @@ static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, return 0; } -void armada_drm_crtc_plane_disable(struct armada_crtc *dcrtc, - struct drm_plane *plane) -{ - u32 sram_para1, dma_ctrl0_mask; - - /* - * Drop our reference on any framebuffer attached to this plane. - * We don't need to NULL this out as drm_plane_force_disable(), - * and __setplane_internal() will do so for an overlay plane, and - * __drm_helper_disable_unused_functions() will do so for the - * primary plane. - */ - if (plane->fb) - drm_framebuffer_put(plane->fb); - - /* Power down the Y/U/V FIFOs */ - sram_para1 = CFG_PDWN16x66 | CFG_PDWN32x66; - - /* Power down most RAMs and FIFOs if this is the primary plane */ - if (plane->type == DRM_PLANE_TYPE_PRIMARY) { - sram_para1 |= CFG_PDWN256x32 | CFG_PDWN256x24 | CFG_PDWN256x8 | - CFG_PDWN32x32 | CFG_PDWN64x66; - dma_ctrl0_mask = CFG_GRA_ENA; - } else { - dma_ctrl0_mask = CFG_DMA_ENA; - } - - spin_lock_irq(&dcrtc->irq_lock); - armada_updatel(0, dma_ctrl0_mask, dcrtc->base + LCD_SPU_DMA_CTRL0); - spin_unlock_irq(&dcrtc->irq_lock); - - armada_updatel(sram_para1, 0, dcrtc->base + LCD_SPU_SRAM_PARA1); -} - /* The mode_config.mutex will be held for this call */ static void armada_drm_crtc_disable(struct drm_crtc *crtc) { - struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); - armada_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); - armada_drm_crtc_plane_disable(dcrtc, crtc->primary); + + /* Disable our primary plane when we disable the CRTC. */ + crtc->primary->funcs->disable_plane(crtc->primary, NULL); } static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = { @@ -879,9 +890,11 @@ static int armada_drm_crtc_cursor_update(struct armada_crtc *dcrtc, bool reload) return 0; } + spin_lock_irq(&dcrtc->irq_lock); para1 = readl_relaxed(dcrtc->base + LCD_SPU_SRAM_PARA1); armada_updatel(CFG_CSB_256x32, CFG_CSB_256x32 | CFG_PDWN256x32, dcrtc->base + LCD_SPU_SRAM_PARA1); + spin_unlock_irq(&dcrtc->irq_lock); /* * Initialize the transparency if the SRAM was powered down. @@ -1021,7 +1034,7 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc, struct drm_modeset_acquire_ctx *ctx) { struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); - struct armada_frame_work *work; + struct armada_plane_work *work; unsigned i; int ret; @@ -1029,11 +1042,10 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc, if (fb->format != crtc->primary->fb->format) return -EINVAL; - work = kmalloc(sizeof(*work), GFP_KERNEL); + work = armada_drm_crtc_alloc_plane_work(dcrtc->crtc.primary); if (!work) return -ENOMEM; - work->work.fn = armada_drm_crtc_complete_frame_work; work->event = event; work->old_fb = dcrtc->crtc.primary->fb; @@ -1047,7 +1059,7 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc, */ drm_framebuffer_get(fb); - ret = armada_drm_crtc_queue_frame_work(dcrtc, work); + ret = armada_drm_plane_work_queue(dcrtc, work); if (ret) { /* Undo our reference above */ drm_framebuffer_put(fb); @@ -1127,14 +1139,195 @@ static const struct drm_crtc_funcs armada_crtc_funcs = { .disable_vblank = armada_drm_crtc_disable_vblank, }; +static void armada_drm_primary_update_state(struct drm_plane_state *state, + struct armada_regs *regs) +{ + struct armada_plane *dplane = drm_to_armada_plane(state->plane); + struct armada_crtc *dcrtc = drm_to_armada_crtc(state->crtc); + struct armada_framebuffer *dfb = drm_fb_to_armada_fb(state->fb); + bool was_disabled; + unsigned int idx = 0; + u32 val; + + val = CFG_GRA_FMT(dfb->fmt) | CFG_GRA_MOD(dfb->mod); + if (dfb->fmt > CFG_420) + val |= CFG_PALETTE_ENA; + if (state->visible) + val |= CFG_GRA_ENA; + if (drm_rect_width(&state->src) >> 16 != drm_rect_width(&state->dst)) + val |= CFG_GRA_HSMOOTH; + + was_disabled = !(dplane->state.ctrl0 & CFG_GRA_ENA); + if (was_disabled) + armada_reg_queue_mod(regs, idx, + 0, CFG_PDWN64x66, LCD_SPU_SRAM_PARA1); + + dplane->state.ctrl0 = val; + dplane->state.src_hw = (drm_rect_height(&state->src) & 0xffff0000) | + drm_rect_width(&state->src) >> 16; + dplane->state.dst_hw = drm_rect_height(&state->dst) << 16 | + drm_rect_width(&state->dst); + dplane->state.dst_yx = state->dst.y1 << 16 | state->dst.x1; + + armada_drm_gra_plane_regs(regs + idx, &dfb->fb, &dplane->state, + state->src.x1 >> 16, state->src.y1 >> 16, + dcrtc->interlaced); + + dplane->state.vsync_update = !was_disabled; + dplane->state.changed = true; +} + +static int armada_drm_primary_update(struct drm_plane *plane, + struct drm_crtc *crtc, struct drm_framebuffer *fb, + int crtc_x, int crtc_y, unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, + struct drm_modeset_acquire_ctx *ctx) +{ + struct armada_plane *dplane = drm_to_armada_plane(plane); + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); + struct armada_plane_work *work; + struct drm_plane_state state = { + .plane = plane, + .crtc = crtc, + .fb = fb, + .src_x = src_x, + .src_y = src_y, + .src_w = src_w, + .src_h = src_h, + .crtc_x = crtc_x, + .crtc_y = crtc_y, + .crtc_w = crtc_w, + .crtc_h = crtc_h, + .rotation = DRM_MODE_ROTATE_0, + }; + const struct drm_rect clip = { + .x2 = crtc->mode.hdisplay, + .y2 = crtc->mode.vdisplay, + }; + int ret; + + ret = drm_atomic_helper_check_plane_state(&state, crtc->state, &clip, 0, + INT_MAX, true, false); + if (ret) + return ret; + + work = &dplane->works[dplane->next_work]; + work->fn = armada_drm_crtc_complete_frame_work; + + if (plane->fb != fb) { + /* + * Take a reference on the new framebuffer - we want to + * hold on to it while the hardware is displaying it. + */ + drm_framebuffer_reference(fb); + + work->old_fb = plane->fb; + } else { + work->old_fb = NULL; + } + + armada_drm_primary_update_state(&state, work->regs); + + if (!dplane->state.changed) + return 0; + + /* Wait for pending work to complete */ + if (armada_drm_plane_work_wait(dplane, HZ / 10) == 0) + armada_drm_plane_work_cancel(dcrtc, dplane); + + if (!dplane->state.vsync_update) { + work->fn(dcrtc, work); + if (work->old_fb) + drm_framebuffer_unreference(work->old_fb); + return 0; + } + + /* Queue it for update on the next interrupt if we are enabled */ + ret = armada_drm_plane_work_queue(dcrtc, work); + if (ret) { + work->fn(dcrtc, work); + if (work->old_fb) + drm_framebuffer_unreference(work->old_fb); + } + + dplane->next_work = !dplane->next_work; + + return 0; +} + +int armada_drm_plane_disable(struct drm_plane *plane, + struct drm_modeset_acquire_ctx *ctx) +{ + struct armada_plane *dplane = drm_to_armada_plane(plane); + struct armada_crtc *dcrtc; + struct armada_plane_work *work; + unsigned int idx = 0; + u32 sram_para1, enable_mask; + + if (!plane->crtc) + return 0; + + /* + * Arrange to power down most RAMs and FIFOs if this is the primary + * plane, otherwise just the YUV FIFOs for the overlay plane. + */ + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + sram_para1 = CFG_PDWN256x32 | CFG_PDWN256x24 | CFG_PDWN256x8 | + CFG_PDWN32x32 | CFG_PDWN64x66; + enable_mask = CFG_GRA_ENA; + } else { + sram_para1 = CFG_PDWN16x66 | CFG_PDWN32x66; + enable_mask = CFG_DMA_ENA; + } + + dplane->state.ctrl0 &= ~enable_mask; + + dcrtc = drm_to_armada_crtc(plane->crtc); + + /* + * Try to disable the plane and drop our ref on the framebuffer + * at the next frame update. If we fail for any reason, disable + * the plane immediately. + */ + work = &dplane->works[dplane->next_work]; + work->fn = armada_drm_crtc_complete_disable_work; + work->cancel = armada_drm_crtc_complete_disable_work; + work->old_fb = plane->fb; + + armada_reg_queue_mod(work->regs, idx, + 0, enable_mask, LCD_SPU_DMA_CTRL0); + armada_reg_queue_mod(work->regs, idx, + sram_para1, 0, LCD_SPU_SRAM_PARA1); + armada_reg_queue_end(work->regs, idx); + + /* Wait for any preceding work to complete, but don't wedge */ + if (WARN_ON(!armada_drm_plane_work_wait(dplane, HZ))) + armada_drm_plane_work_cancel(dcrtc, dplane); + + if (armada_drm_plane_work_queue(dcrtc, work)) { + work->fn(dcrtc, work); + if (work->old_fb) + drm_framebuffer_unreference(work->old_fb); + } + + dplane->next_work = !dplane->next_work; + + return 0; +} + static const struct drm_plane_funcs armada_primary_plane_funcs = { - .update_plane = drm_primary_helper_update, - .disable_plane = drm_primary_helper_disable, + .update_plane = armada_drm_primary_update, + .disable_plane = armada_drm_plane_disable, .destroy = drm_primary_helper_destroy, }; int armada_drm_plane_init(struct armada_plane *plane) { + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(plane->works); i++) + plane->works[i].plane = &plane->base; + init_waitqueue_head(&plane->frame_wait); return 0; @@ -1225,17 +1418,13 @@ static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev, ret = devm_request_irq(dev, irq, armada_drm_irq, 0, "armada_drm_crtc", dcrtc); - if (ret < 0) { - kfree(dcrtc); - return ret; - } + if (ret < 0) + goto err_crtc; if (dcrtc->variant->init) { ret = dcrtc->variant->init(dcrtc, dev); - if (ret) { - kfree(dcrtc); - return ret; - } + if (ret) + goto err_crtc; } /* Ensure AXI pipeline is enabled */ @@ -1246,13 +1435,15 @@ static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev, dcrtc->crtc.port = port; primary = kzalloc(sizeof(*primary), GFP_KERNEL); - if (!primary) - return -ENOMEM; + if (!primary) { + ret = -ENOMEM; + goto err_crtc; + } ret = armada_drm_plane_init(primary); if (ret) { kfree(primary); - return ret; + goto err_crtc; } ret = drm_universal_plane_init(drm, &primary->base, 0, @@ -1263,7 +1454,7 @@ static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev, DRM_PLANE_TYPE_PRIMARY, NULL); if (ret) { kfree(primary); - return ret; + goto err_crtc; } ret = drm_crtc_init_with_planes(drm, &dcrtc->crtc, &primary->base, NULL, @@ -1282,6 +1473,9 @@ static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev, err_crtc_init: primary->base.funcs->destroy(&primary->base); +err_crtc: + kfree(dcrtc); + return ret; } diff --git a/drivers/gpu/drm/armada/armada_crtc.h b/drivers/gpu/drm/armada/armada_crtc.h index bab11f483575..445829b8877a 100644 --- a/drivers/gpu/drm/armada/armada_crtc.h +++ b/drivers/gpu/drm/armada/armada_crtc.h @@ -36,21 +36,31 @@ struct armada_plane; struct armada_variant; struct armada_plane_work { - void (*fn)(struct armada_crtc *, - struct armada_plane *, - struct armada_plane_work *); + void (*fn)(struct armada_crtc *, struct armada_plane_work *); + void (*cancel)(struct armada_crtc *, struct armada_plane_work *); + bool need_kfree; + struct drm_plane *plane; + struct drm_framebuffer *old_fb; + struct drm_pending_vblank_event *event; + struct armada_regs regs[14]; }; struct armada_plane_state { + u16 src_x; + u16 src_y; u32 src_hw; u32 dst_hw; u32 dst_yx; u32 ctrl0; + bool changed; + bool vsync_update; }; struct armada_plane { struct drm_plane base; wait_queue_head_t frame_wait; + bool next_work; + struct armada_plane_work works[2]; struct armada_plane_work *work; struct armada_plane_state state; }; @@ -58,10 +68,10 @@ struct armada_plane { int armada_drm_plane_init(struct armada_plane *plane); int armada_drm_plane_work_queue(struct armada_crtc *dcrtc, - struct armada_plane *plane, struct armada_plane_work *work); + struct armada_plane_work *work); int armada_drm_plane_work_wait(struct armada_plane *plane, long timeout); -struct armada_plane_work *armada_drm_plane_work_cancel( - struct armada_crtc *dcrtc, struct armada_plane *plane); +void armada_drm_plane_work_cancel(struct armada_crtc *dcrtc, + struct armada_plane *plane); void armada_drm_plane_calc_addrs(u32 *addrs, struct drm_framebuffer *fb, int x, int y); @@ -104,8 +114,8 @@ struct armada_crtc { void armada_drm_crtc_update_regs(struct armada_crtc *, struct armada_regs *); -void armada_drm_crtc_plane_disable(struct armada_crtc *dcrtc, - struct drm_plane *plane); +int armada_drm_plane_disable(struct drm_plane *plane, + struct drm_modeset_acquire_ctx *ctx); extern struct platform_driver armada_lcd_platform_driver; diff --git a/drivers/gpu/drm/armada/armada_overlay.c b/drivers/gpu/drm/armada/armada_overlay.c index b411b608821a..77b55adaa2ac 100644 --- a/drivers/gpu/drm/armada/armada_overlay.c +++ b/drivers/gpu/drm/armada/armada_overlay.c @@ -7,7 +7,7 @@ * published by the Free Software Foundation. */ #include <drm/drmP.h> -#include <drm/drm_plane_helper.h> +#include <drm/drm_atomic_helper.h> #include "armada_crtc.h" #include "armada_drm.h" #include "armada_fb.h" @@ -32,11 +32,6 @@ struct armada_ovl_plane_properties { struct armada_ovl_plane { struct armada_plane base; - struct drm_framebuffer *old_fb; - struct { - struct armada_plane_work work; - struct armada_regs regs[13]; - } vbl; struct armada_ovl_plane_properties prop; }; #define drm_to_armada_ovl_plane(p) \ @@ -67,218 +62,204 @@ armada_ovl_update_attr(struct armada_ovl_plane_properties *prop, spin_unlock_irq(&dcrtc->irq_lock); } -static void armada_ovl_retire_fb(struct armada_ovl_plane *dplane, - struct drm_framebuffer *fb) -{ - struct drm_framebuffer *old_fb; - - old_fb = xchg(&dplane->old_fb, fb); - - if (old_fb) - armada_drm_queue_unref_work(dplane->base.base.dev, old_fb); -} - /* === Plane support === */ static void armada_ovl_plane_work(struct armada_crtc *dcrtc, - struct armada_plane *plane, struct armada_plane_work *work) + struct armada_plane_work *work) { - struct armada_ovl_plane *dplane = container_of(plane, struct armada_ovl_plane, base); + unsigned long flags; - trace_armada_ovl_plane_work(&dcrtc->crtc, &plane->base); + trace_armada_ovl_plane_work(&dcrtc->crtc, work->plane); - armada_drm_crtc_update_regs(dcrtc, dplane->vbl.regs); - armada_ovl_retire_fb(dplane, NULL); + spin_lock_irqsave(&dcrtc->irq_lock, flags); + armada_drm_crtc_update_regs(dcrtc, work->regs); + spin_unlock_irqrestore(&dcrtc->irq_lock, flags); } -static int -armada_ovl_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, - struct drm_framebuffer *fb, - int crtc_x, int crtc_y, unsigned crtc_w, unsigned crtc_h, - uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, - struct drm_modeset_acquire_ctx *ctx) +static void armada_ovl_plane_update_state(struct drm_plane_state *state, + struct armada_regs *regs) { - struct armada_ovl_plane *dplane = drm_to_armada_ovl_plane(plane); - struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); - struct drm_rect src = { - .x1 = src_x, - .y1 = src_y, - .x2 = src_x + src_w, - .y2 = src_y + src_h, - }; - struct drm_rect dest = { - .x1 = crtc_x, - .y1 = crtc_y, - .x2 = crtc_x + crtc_w, - .y2 = crtc_y + crtc_h, - }; - const struct drm_rect clip = { - .x2 = crtc->mode.hdisplay, - .y2 = crtc->mode.vdisplay, - }; - uint32_t val, ctrl0; - unsigned idx = 0; - bool visible; - int ret; - - trace_armada_ovl_plane_update(plane, crtc, fb, - crtc_x, crtc_y, crtc_w, crtc_h, - src_x, src_y, src_w, src_h); - - ret = drm_plane_helper_check_update(plane, crtc, fb, &src, &dest, &clip, - DRM_MODE_ROTATE_0, - 0, INT_MAX, true, false, &visible); - if (ret) - return ret; - - ctrl0 = CFG_DMA_FMT(drm_fb_to_armada_fb(fb)->fmt) | - CFG_DMA_MOD(drm_fb_to_armada_fb(fb)->mod) | - CFG_CBSH_ENA | CFG_DMA_HSMOOTH | CFG_DMA_ENA; - - /* Does the position/size result in nothing to display? */ - if (!visible) - ctrl0 &= ~CFG_DMA_ENA; - - if (!dcrtc->plane) { - dcrtc->plane = plane; - armada_ovl_update_attr(&dplane->prop, dcrtc); - } - - /* FIXME: overlay on an interlaced display */ - /* Just updating the position/size? */ - if (plane->fb == fb && dplane->base.state.ctrl0 == ctrl0) { - val = (drm_rect_height(&src) & 0xffff0000) | - drm_rect_width(&src) >> 16; - dplane->base.state.src_hw = val; - writel_relaxed(val, dcrtc->base + LCD_SPU_DMA_HPXL_VLN); - - val = drm_rect_height(&dest) << 16 | drm_rect_width(&dest); - dplane->base.state.dst_hw = val; - writel_relaxed(val, dcrtc->base + LCD_SPU_DZM_HPXL_VLN); - - val = dest.y1 << 16 | dest.x1; - dplane->base.state.dst_yx = val; - writel_relaxed(val, dcrtc->base + LCD_SPU_DMA_OVSA_HPXL_VLN); - - return 0; - } else if (~dplane->base.state.ctrl0 & ctrl0 & CFG_DMA_ENA) { + struct armada_ovl_plane *dplane = drm_to_armada_ovl_plane(state->plane); + struct armada_framebuffer *dfb = drm_fb_to_armada_fb(state->fb); + const struct drm_format_info *format; + unsigned int idx = 0; + bool fb_changed; + u32 val, ctrl0; + u16 src_x, src_y; + + ctrl0 = CFG_DMA_FMT(dfb->fmt) | CFG_DMA_MOD(dfb->mod) | CFG_CBSH_ENA; + if (state->visible) + ctrl0 |= CFG_DMA_ENA; + if (drm_rect_width(&state->src) >> 16 != drm_rect_width(&state->dst)) + ctrl0 |= CFG_DMA_HSMOOTH; + + /* + * Shifting a YUV packed format image by one pixel causes the U/V + * planes to swap. Compensate for it by also toggling the UV swap. + */ + format = dfb->fb.format; + if (format->num_planes == 1 && state->src.x1 >> 16 & (format->hsub - 1)) + ctrl0 ^= CFG_DMA_MOD(CFG_SWAPUV); + + if (~dplane->base.state.ctrl0 & ctrl0 & CFG_DMA_ENA) { /* Power up the Y/U/V FIFOs on ENA 0->1 transitions */ - armada_updatel(0, CFG_PDWN16x66 | CFG_PDWN32x66, - dcrtc->base + LCD_SPU_SRAM_PARA1); + armada_reg_queue_mod(regs, idx, + 0, CFG_PDWN16x66 | CFG_PDWN32x66, + LCD_SPU_SRAM_PARA1); } - if (armada_drm_plane_work_wait(&dplane->base, HZ / 25) == 0) - armada_drm_plane_work_cancel(dcrtc, &dplane->base); - - if (plane->fb != fb) { - u32 addrs[3], pixel_format; - int num_planes, hsub; - - /* - * Take a reference on the new framebuffer - we want to - * hold on to it while the hardware is displaying it. - */ - drm_framebuffer_get(fb); - - if (plane->fb) - armada_ovl_retire_fb(dplane, plane->fb); + fb_changed = dplane->base.base.fb != &dfb->fb || + dplane->base.state.src_x != state->src.x1 >> 16 || + dplane->base.state.src_y != state->src.y1 >> 16; - src_y = src.y1 >> 16; - src_x = src.x1 >> 16; + dplane->base.state.vsync_update = fb_changed; - armada_drm_plane_calc_addrs(addrs, fb, src_x, src_y); + /* FIXME: overlay on an interlaced display */ + if (fb_changed) { + u32 addrs[3]; - pixel_format = fb->format->format; - hsub = drm_format_horz_chroma_subsampling(pixel_format); - num_planes = fb->format->num_planes; + dplane->base.state.src_y = src_y = state->src.y1 >> 16; + dplane->base.state.src_x = src_x = state->src.x1 >> 16; - /* - * Annoyingly, shifting a YUYV-format image by one pixel - * causes the U/V planes to toggle. Toggle the UV swap. - * (Unfortunately, this causes momentary colour flickering.) - */ - if (src_x & (hsub - 1) && num_planes == 1) - ctrl0 ^= CFG_DMA_MOD(CFG_SWAPUV); + armada_drm_plane_calc_addrs(addrs, &dfb->fb, src_x, src_y); - armada_reg_queue_set(dplane->vbl.regs, idx, addrs[0], + armada_reg_queue_set(regs, idx, addrs[0], LCD_SPU_DMA_START_ADDR_Y0); - armada_reg_queue_set(dplane->vbl.regs, idx, addrs[1], + armada_reg_queue_set(regs, idx, addrs[1], LCD_SPU_DMA_START_ADDR_U0); - armada_reg_queue_set(dplane->vbl.regs, idx, addrs[2], + armada_reg_queue_set(regs, idx, addrs[2], LCD_SPU_DMA_START_ADDR_V0); - armada_reg_queue_set(dplane->vbl.regs, idx, addrs[0], + armada_reg_queue_set(regs, idx, addrs[0], LCD_SPU_DMA_START_ADDR_Y1); - armada_reg_queue_set(dplane->vbl.regs, idx, addrs[1], + armada_reg_queue_set(regs, idx, addrs[1], LCD_SPU_DMA_START_ADDR_U1); - armada_reg_queue_set(dplane->vbl.regs, idx, addrs[2], + armada_reg_queue_set(regs, idx, addrs[2], LCD_SPU_DMA_START_ADDR_V1); - val = fb->pitches[0] << 16 | fb->pitches[0]; - armada_reg_queue_set(dplane->vbl.regs, idx, val, + val = dfb->fb.pitches[0] << 16 | dfb->fb.pitches[0]; + armada_reg_queue_set(regs, idx, val, LCD_SPU_DMA_PITCH_YC); - val = fb->pitches[1] << 16 | fb->pitches[2]; - armada_reg_queue_set(dplane->vbl.regs, idx, val, + val = dfb->fb.pitches[1] << 16 | dfb->fb.pitches[2]; + armada_reg_queue_set(regs, idx, val, LCD_SPU_DMA_PITCH_UV); } - val = (drm_rect_height(&src) & 0xffff0000) | drm_rect_width(&src) >> 16; + val = (drm_rect_height(&state->src) & 0xffff0000) | + drm_rect_width(&state->src) >> 16; if (dplane->base.state.src_hw != val) { dplane->base.state.src_hw = val; - armada_reg_queue_set(dplane->vbl.regs, idx, val, + armada_reg_queue_set(regs, idx, val, LCD_SPU_DMA_HPXL_VLN); } - val = drm_rect_height(&dest) << 16 | drm_rect_width(&dest); + val = drm_rect_height(&state->dst) << 16 | drm_rect_width(&state->dst); if (dplane->base.state.dst_hw != val) { dplane->base.state.dst_hw = val; - armada_reg_queue_set(dplane->vbl.regs, idx, val, + armada_reg_queue_set(regs, idx, val, LCD_SPU_DZM_HPXL_VLN); } - val = dest.y1 << 16 | dest.x1; + val = state->dst.y1 << 16 | state->dst.x1; if (dplane->base.state.dst_yx != val) { dplane->base.state.dst_yx = val; - armada_reg_queue_set(dplane->vbl.regs, idx, val, + armada_reg_queue_set(regs, idx, val, LCD_SPU_DMA_OVSA_HPXL_VLN); } if (dplane->base.state.ctrl0 != ctrl0) { dplane->base.state.ctrl0 = ctrl0; - armada_reg_queue_mod(dplane->vbl.regs, idx, ctrl0, + armada_reg_queue_mod(regs, idx, ctrl0, CFG_CBSH_ENA | CFG_DMAFORMAT | CFG_DMA_FTOGGLE | CFG_DMA_HSMOOTH | CFG_DMA_TSTMODE | CFG_DMA_MOD(CFG_SWAPRB | CFG_SWAPUV | CFG_SWAPYU | CFG_YUV2RGB) | CFG_DMA_ENA, LCD_SPU_DMA_CTRL0); + dplane->base.state.vsync_update = true; } - if (idx) { - armada_reg_queue_end(dplane->vbl.regs, idx); - armada_drm_plane_work_queue(dcrtc, &dplane->base, - &dplane->vbl.work); - } - return 0; + + dplane->base.state.changed = idx != 0; + + armada_reg_queue_end(regs, idx); } -static int armada_ovl_plane_disable(struct drm_plane *plane, - struct drm_modeset_acquire_ctx *ctx) +static int +armada_ovl_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, + struct drm_framebuffer *fb, + int crtc_x, int crtc_y, unsigned crtc_w, unsigned crtc_h, + uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, + struct drm_modeset_acquire_ctx *ctx) { struct armada_ovl_plane *dplane = drm_to_armada_ovl_plane(plane); - struct drm_framebuffer *fb; - struct armada_crtc *dcrtc; + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); + struct armada_plane_work *work; + struct drm_plane_state state = { + .plane = plane, + .crtc = crtc, + .fb = fb, + .src_x = src_x, + .src_y = src_y, + .src_w = src_w, + .src_h = src_h, + .crtc_x = crtc_x, + .crtc_y = crtc_y, + .crtc_w = crtc_w, + .crtc_h = crtc_h, + .rotation = DRM_MODE_ROTATE_0, + }; + const struct drm_rect clip = { + .x2 = crtc->mode.hdisplay, + .y2 = crtc->mode.vdisplay, + }; + int ret; - if (!dplane->base.base.crtc) + trace_armada_ovl_plane_update(plane, crtc, fb, + crtc_x, crtc_y, crtc_w, crtc_h, + src_x, src_y, src_w, src_h); + + ret = drm_atomic_helper_check_plane_state(&state, crtc->state, &clip, 0, + INT_MAX, true, false); + if (ret) + return ret; + + work = &dplane->base.works[dplane->base.next_work]; + + if (plane->fb != fb) { + /* + * Take a reference on the new framebuffer - we want to + * hold on to it while the hardware is displaying it. + */ + drm_framebuffer_reference(fb); + + work->old_fb = plane->fb; + } else { + work->old_fb = NULL; + } + + armada_ovl_plane_update_state(&state, work->regs); + + if (!dplane->base.state.changed) return 0; - dcrtc = drm_to_armada_crtc(dplane->base.base.crtc); + /* Wait for pending work to complete */ + if (armada_drm_plane_work_wait(&dplane->base, HZ / 25) == 0) + armada_drm_plane_work_cancel(dcrtc, &dplane->base); + + /* Just updating the position/size? */ + if (!dplane->base.state.vsync_update) { + armada_ovl_plane_work(dcrtc, work); + return 0; + } - armada_drm_plane_work_cancel(dcrtc, &dplane->base); - armada_drm_crtc_plane_disable(dcrtc, plane); + if (!dcrtc->plane) { + dcrtc->plane = plane; + armada_ovl_update_attr(&dplane->prop, dcrtc); + } - dcrtc->plane = NULL; - dplane->base.state.ctrl0 = 0; + /* Queue it for update on the next interrupt if we are enabled */ + ret = armada_drm_plane_work_queue(dcrtc, work); + if (ret) + DRM_ERROR("failed to queue plane work: %d\n", ret); - fb = xchg(&dplane->old_fb, NULL); - if (fb) - drm_framebuffer_put(fb); + dplane->base.next_work = !dplane->base.next_work; return 0; } @@ -362,7 +343,7 @@ static int armada_ovl_plane_set_property(struct drm_plane *plane, static const struct drm_plane_funcs armada_ovl_plane_funcs = { .update_plane = armada_ovl_plane_update, - .disable_plane = armada_ovl_plane_disable, + .disable_plane = armada_drm_plane_disable, .destroy = armada_ovl_plane_destroy, .set_property = armada_ovl_plane_set_property, }; @@ -454,7 +435,8 @@ int armada_overlay_plane_create(struct drm_device *dev, unsigned long crtcs) return ret; } - dplane->vbl.work.fn = armada_ovl_plane_work; + dplane->base.works[0].fn = armada_ovl_plane_work; + dplane->base.works[1].fn = armada_ovl_plane_work; ret = drm_universal_plane_init(dev, &dplane->base.base, crtcs, &armada_ovl_plane_funcs, diff --git a/drivers/gpu/drm/armada/armada_trace.h b/drivers/gpu/drm/armada/armada_trace.h index 8dbfea7a00fe..f03a56bda596 100644 --- a/drivers/gpu/drm/armada/armada_trace.h +++ b/drivers/gpu/drm/armada/armada_trace.h @@ -34,14 +34,34 @@ TRACE_EVENT(armada_ovl_plane_update, __field(struct drm_plane *, plane) __field(struct drm_crtc *, crtc) __field(struct drm_framebuffer *, fb) + __field(int, crtc_x) + __field(int, crtc_y) + __field(unsigned int, crtc_w) + __field(unsigned int, crtc_h) + __field(u32, src_x) + __field(u32, src_y) + __field(u32, src_w) + __field(u32, src_h) ), TP_fast_assign( __entry->plane = plane; __entry->crtc = crtc; __entry->fb = fb; + __entry->crtc_x = crtc_x; + __entry->crtc_y = crtc_y; + __entry->crtc_w = crtc_w; + __entry->crtc_h = crtc_h; + __entry->src_x = src_x; + __entry->src_y = src_y; + __entry->src_w = src_w; + __entry->src_h = src_h; ), - TP_printk("plane %p crtc %p fb %p", - __entry->plane, __entry->crtc, __entry->fb) + TP_printk("plane %p crtc %p fb %p crtc @ (%d,%d, %ux%u) src @ (%u,%u, %ux%u)", + __entry->plane, __entry->crtc, __entry->fb, + __entry->crtc_x, __entry->crtc_y, + __entry->crtc_w, __entry->crtc_h, + __entry->src_x >> 16, __entry->src_y >> 16, + __entry->src_w >> 16, __entry->src_h >> 16) ); TRACE_EVENT(armada_ovl_plane_work, |