diff options
Diffstat (limited to 'drivers/gpu/drm/rcar-du/rcar_du_plane.c')
-rw-r--r-- | drivers/gpu/drm/rcar-du/rcar_du_plane.c | 465 |
1 files changed, 403 insertions, 62 deletions
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_plane.c b/drivers/gpu/drm/rcar-du/rcar_du_plane.c index c3ed9522c0e1..8460ae1ffa4b 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_plane.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_plane.c @@ -1,7 +1,7 @@ /* * rcar_du_plane.c -- R-Car Display Unit Planes * - * Copyright (C) 2013-2014 Renesas Electronics Corporation + * Copyright (C) 2013-2015 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) * @@ -12,6 +12,7 @@ */ #include <drm/drmP.h> +#include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> @@ -20,21 +21,300 @@ #include <drm/drm_plane_helper.h> #include "rcar_du_drv.h" +#include "rcar_du_group.h" #include "rcar_du_kms.h" #include "rcar_du_plane.h" #include "rcar_du_regs.h" -#define RCAR_DU_COLORKEY_NONE (0 << 24) -#define RCAR_DU_COLORKEY_SOURCE (1 << 24) -#define RCAR_DU_COLORKEY_MASK (1 << 24) +/* ----------------------------------------------------------------------------- + * Atomic hardware plane allocator + * + * The hardware plane allocator is solely based on the atomic plane states + * without keeping any external state to avoid races between .atomic_check() + * and .atomic_commit(). + * + * The core idea is to avoid using a free planes bitmask that would need to be + * shared between check and commit handlers with a collective knowledge based on + * the allocated hardware plane(s) for each KMS plane. The allocator then loops + * over all plane states to compute the free planes bitmask, allocates hardware + * planes based on that bitmask, and stores the result back in the plane states. + * + * For this to work we need to access the current state of planes not touched by + * the atomic update. To ensure that it won't be modified, we need to lock all + * planes using drm_atomic_get_plane_state(). This effectively serializes atomic + * updates from .atomic_check() up to completion (when swapping the states if + * the check step has succeeded) or rollback (when freeing the states if the + * check step has failed). + * + * Allocation is performed in the .atomic_check() handler and applied + * automatically when the core swaps the old and new states. + */ + +static bool rcar_du_plane_needs_realloc(struct rcar_du_plane *plane, + struct rcar_du_plane_state *new_state) +{ + struct rcar_du_plane_state *cur_state; + + cur_state = to_rcar_plane_state(plane->plane.state); + + /* Lowering the number of planes doesn't strictly require reallocation + * as the extra hardware plane will be freed when committing, but doing + * so could lead to more fragmentation. + */ + if (!cur_state->format || + cur_state->format->planes != new_state->format->planes) + return true; + + /* Reallocate hardware planes if the source has changed. */ + if (cur_state->source != new_state->source) + return true; -static u32 rcar_du_plane_read(struct rcar_du_group *rgrp, - unsigned int index, u32 reg) + return false; +} + +static unsigned int rcar_du_plane_hwmask(struct rcar_du_plane_state *state) +{ + unsigned int mask; + + if (state->hwindex == -1) + return 0; + + mask = 1 << state->hwindex; + if (state->format->planes == 2) + mask |= 1 << ((state->hwindex + 1) % 8); + + return mask; +} + +/* + * The R8A7790 DU can source frames directly from the VSP1 devices VSPD0 and + * VSPD1. VSPD0 feeds DU0/1 plane 0, and VSPD1 feeds either DU2 plane 0 or + * DU0/1 plane 1. + * + * Allocate the correct fixed plane when sourcing frames from VSPD0 or VSPD1, + * and allocate planes in reverse index order otherwise to ensure maximum + * availability of planes 0 and 1. + * + * The caller is responsible for ensuring that the requested source is + * compatible with the DU revision. + */ +static int rcar_du_plane_hwalloc(struct rcar_du_plane *plane, + struct rcar_du_plane_state *state, + unsigned int free) { - return rcar_du_read(rgrp->dev, - rgrp->mmio_offset + index * PLANE_OFF + reg); + unsigned int num_planes = state->format->planes; + int fixed = -1; + int i; + + if (state->source == RCAR_DU_PLANE_VSPD0) { + /* VSPD0 feeds plane 0 on DU0/1. */ + if (plane->group->index != 0) + return -EINVAL; + + fixed = 0; + } else if (state->source == RCAR_DU_PLANE_VSPD1) { + /* VSPD1 feeds plane 1 on DU0/1 or plane 0 on DU2. */ + fixed = plane->group->index == 0 ? 1 : 0; + } + + if (fixed >= 0) + return free & (1 << fixed) ? fixed : -EBUSY; + + for (i = RCAR_DU_NUM_HW_PLANES - 1; i >= 0; --i) { + if (!(free & (1 << i))) + continue; + + if (num_planes == 1 || free & (1 << ((i + 1) % 8))) + break; + } + + return i < 0 ? -EBUSY : i; } +int rcar_du_atomic_check_planes(struct drm_device *dev, + struct drm_atomic_state *state) +{ + struct rcar_du_device *rcdu = dev->dev_private; + unsigned int group_freed_planes[RCAR_DU_MAX_GROUPS] = { 0, }; + unsigned int group_free_planes[RCAR_DU_MAX_GROUPS] = { 0, }; + bool needs_realloc = false; + unsigned int groups = 0; + unsigned int i; + + /* Check if hardware planes need to be reallocated. */ + for (i = 0; i < dev->mode_config.num_total_plane; ++i) { + struct rcar_du_plane_state *plane_state; + struct rcar_du_plane *plane; + unsigned int index; + + if (!state->planes[i]) + continue; + + plane = to_rcar_plane(state->planes[i]); + plane_state = to_rcar_plane_state(state->plane_states[i]); + + dev_dbg(rcdu->dev, "%s: checking plane (%u,%tu)\n", __func__, + plane->group->index, plane - plane->group->planes); + + /* If the plane is being disabled we don't need to go through + * the full reallocation procedure. Just mark the hardware + * plane(s) as freed. + */ + if (!plane_state->format) { + dev_dbg(rcdu->dev, "%s: plane is being disabled\n", + __func__); + index = plane - plane->group->planes; + group_freed_planes[plane->group->index] |= 1 << index; + plane_state->hwindex = -1; + continue; + } + + /* If the plane needs to be reallocated mark it as such, and + * mark the hardware plane(s) as free. + */ + if (rcar_du_plane_needs_realloc(plane, plane_state)) { + dev_dbg(rcdu->dev, "%s: plane needs reallocation\n", + __func__); + groups |= 1 << plane->group->index; + needs_realloc = true; + + index = plane - plane->group->planes; + group_freed_planes[plane->group->index] |= 1 << index; + plane_state->hwindex = -1; + } + } + + if (!needs_realloc) + return 0; + + /* Grab all plane states for the groups that need reallocation to ensure + * locking and avoid racy updates. This serializes the update operation, + * but there's not much we can do about it as that's the hardware + * design. + * + * Compute the used planes mask for each group at the same time to avoid + * looping over the planes separately later. + */ + while (groups) { + unsigned int index = ffs(groups) - 1; + struct rcar_du_group *group = &rcdu->groups[index]; + unsigned int used_planes = 0; + + dev_dbg(rcdu->dev, "%s: finding free planes for group %u\n", + __func__, index); + + for (i = 0; i < group->num_planes; ++i) { + struct rcar_du_plane *plane = &group->planes[i]; + struct rcar_du_plane_state *plane_state; + struct drm_plane_state *s; + + s = drm_atomic_get_plane_state(state, &plane->plane); + if (IS_ERR(s)) + return PTR_ERR(s); + + /* If the plane has been freed in the above loop its + * hardware planes must not be added to the used planes + * bitmask. However, the current state doesn't reflect + * the free state yet, as we've modified the new state + * above. Use the local freed planes list to check for + * that condition instead. + */ + if (group_freed_planes[index] & (1 << i)) { + dev_dbg(rcdu->dev, + "%s: plane (%u,%tu) has been freed, skipping\n", + __func__, plane->group->index, + plane - plane->group->planes); + continue; + } + + plane_state = to_rcar_plane_state(plane->plane.state); + used_planes |= rcar_du_plane_hwmask(plane_state); + + dev_dbg(rcdu->dev, + "%s: plane (%u,%tu) uses %u hwplanes (index %d)\n", + __func__, plane->group->index, + plane - plane->group->planes, + plane_state->format ? + plane_state->format->planes : 0, + plane_state->hwindex); + } + + group_free_planes[index] = 0xff & ~used_planes; + groups &= ~(1 << index); + + dev_dbg(rcdu->dev, "%s: group %u free planes mask 0x%02x\n", + __func__, index, group_free_planes[index]); + } + + /* Reallocate hardware planes for each plane that needs it. */ + for (i = 0; i < dev->mode_config.num_total_plane; ++i) { + struct rcar_du_plane_state *plane_state; + struct rcar_du_plane *plane; + unsigned int crtc_planes; + unsigned int free; + int idx; + + if (!state->planes[i]) + continue; + + plane = to_rcar_plane(state->planes[i]); + plane_state = to_rcar_plane_state(state->plane_states[i]); + + dev_dbg(rcdu->dev, "%s: allocating plane (%u,%tu)\n", __func__, + plane->group->index, plane - plane->group->planes); + + /* Skip planes that are being disabled or don't need to be + * reallocated. + */ + if (!plane_state->format || + !rcar_du_plane_needs_realloc(plane, plane_state)) + continue; + + /* Try to allocate the plane from the free planes currently + * associated with the target CRTC to avoid restarting the CRTC + * group and thus minimize flicker. If it fails fall back to + * allocating from all free planes. + */ + crtc_planes = to_rcar_crtc(plane_state->state.crtc)->index % 2 + ? plane->group->dptsr_planes + : ~plane->group->dptsr_planes; + free = group_free_planes[plane->group->index]; + + idx = rcar_du_plane_hwalloc(plane, plane_state, + free & crtc_planes); + if (idx < 0) + idx = rcar_du_plane_hwalloc(plane, plane_state, + free); + if (idx < 0) { + dev_dbg(rcdu->dev, "%s: no available hardware plane\n", + __func__); + return idx; + } + + dev_dbg(rcdu->dev, "%s: allocated %u hwplanes (index %u)\n", + __func__, plane_state->format->planes, idx); + + plane_state->hwindex = idx; + + group_free_planes[plane->group->index] &= + ~rcar_du_plane_hwmask(plane_state); + + dev_dbg(rcdu->dev, "%s: group %u free planes mask 0x%02x\n", + __func__, plane->group->index, + group_free_planes[plane->group->index]); + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Plane Setup + */ + +#define RCAR_DU_COLORKEY_NONE (0 << 24) +#define RCAR_DU_COLORKEY_SOURCE (1 << 24) +#define RCAR_DU_COLORKEY_MASK (1 << 24) + static void rcar_du_plane_write(struct rcar_du_group *rgrp, unsigned int index, u32 reg, u32 data) { @@ -42,34 +322,45 @@ static void rcar_du_plane_write(struct rcar_du_group *rgrp, data); } -static void rcar_du_plane_setup_fb(struct rcar_du_plane *plane) +static void rcar_du_plane_setup_scanout(struct rcar_du_group *rgrp, + const struct rcar_du_plane_state *state) { - struct rcar_du_plane_state *state = - to_rcar_plane_state(plane->plane.state); - struct drm_framebuffer *fb = plane->plane.state->fb; - struct rcar_du_group *rgrp = plane->group; unsigned int src_x = state->state.src_x >> 16; unsigned int src_y = state->state.src_y >> 16; unsigned int index = state->hwindex; - struct drm_gem_cma_object *gem; + unsigned int pitch; bool interlaced; - u32 mwr; + u32 dma[2]; interlaced = state->state.crtc->state->adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE; + if (state->source == RCAR_DU_PLANE_MEMORY) { + struct drm_framebuffer *fb = state->state.fb; + struct drm_gem_cma_object *gem; + unsigned int i; + + if (state->format->planes == 2) + pitch = fb->pitches[0]; + else + pitch = fb->pitches[0] * 8 / state->format->bpp; + + for (i = 0; i < state->format->planes; ++i) { + gem = drm_fb_cma_get_gem_obj(fb, i); + dma[i] = gem->paddr + fb->offsets[i]; + } + } else { + pitch = state->state.src_w >> 16; + dma[0] = 0; + dma[1] = 0; + } + /* Memory pitch (expressed in pixels). Must be doubled for interlaced * operation with 32bpp formats. */ - if (state->format->planes == 2) - mwr = fb->pitches[0]; - else - mwr = fb->pitches[0] * 8 / state->format->bpp; - - if (interlaced && state->format->bpp == 32) - mwr *= 2; - - rcar_du_plane_write(rgrp, index, PnMWR, mwr); + rcar_du_plane_write(rgrp, index, PnMWR, + (interlaced && state->format->bpp == 32) ? + pitch * 2 : pitch); /* The Y position is expressed in raster line units and must be doubled * for 32bpp formats, according to the R8A7790 datasheet. No mention of @@ -87,30 +378,25 @@ static void rcar_du_plane_setup_fb(struct rcar_du_plane *plane) rcar_du_plane_write(rgrp, index, PnSPYR, src_y * (!interlaced && state->format->bpp == 32 ? 2 : 1)); - gem = drm_fb_cma_get_gem_obj(fb, 0); - rcar_du_plane_write(rgrp, index, PnDSA0R, gem->paddr + fb->offsets[0]); + rcar_du_plane_write(rgrp, index, PnDSA0R, dma[0]); if (state->format->planes == 2) { index = (index + 1) % 8; - rcar_du_plane_write(rgrp, index, PnMWR, fb->pitches[0]); + rcar_du_plane_write(rgrp, index, PnMWR, pitch); rcar_du_plane_write(rgrp, index, PnSPXR, src_x); rcar_du_plane_write(rgrp, index, PnSPYR, src_y * (state->format->bpp == 16 ? 2 : 1) / 2); - gem = drm_fb_cma_get_gem_obj(fb, 1); - rcar_du_plane_write(rgrp, index, PnDSA0R, - gem->paddr + fb->offsets[1]); + rcar_du_plane_write(rgrp, index, PnDSA0R, dma[1]); } } -static void rcar_du_plane_setup_mode(struct rcar_du_plane *plane, - unsigned int index) +static void rcar_du_plane_setup_mode(struct rcar_du_group *rgrp, + unsigned int index, + const struct rcar_du_plane_state *state) { - struct rcar_du_plane_state *state = - to_rcar_plane_state(plane->plane.state); - struct rcar_du_group *rgrp = plane->group; u32 colorkey; u32 pnmr; @@ -168,12 +454,10 @@ static void rcar_du_plane_setup_mode(struct rcar_du_plane *plane, } } -static void __rcar_du_plane_setup(struct rcar_du_plane *plane, - unsigned int index) +static void rcar_du_plane_setup_format_gen2(struct rcar_du_group *rgrp, + unsigned int index, + const struct rcar_du_plane_state *state) { - struct rcar_du_plane_state *state = - to_rcar_plane_state(plane->plane.state); - struct rcar_du_group *rgrp = plane->group; u32 ddcr2 = PnDDCR2_CODE; u32 ddcr4; @@ -182,11 +466,8 @@ static void __rcar_du_plane_setup(struct rcar_du_plane *plane, * The data format is selected by the DDDF field in PnMR and the EDF * field in DDCR4. */ - ddcr4 = rcar_du_plane_read(rgrp, index, PnDDCR4); - ddcr4 &= ~PnDDCR4_EDF_MASK; - ddcr4 |= state->format->edf | PnDDCR4_CODE; - rcar_du_plane_setup_mode(plane, index); + rcar_du_plane_setup_mode(rgrp, index, state); if (state->format->planes == 2) { if (state->hwindex != index) { @@ -204,31 +485,72 @@ static void __rcar_du_plane_setup(struct rcar_du_plane *plane, } rcar_du_plane_write(rgrp, index, PnDDCR2, ddcr2); + + ddcr4 = state->format->edf | PnDDCR4_CODE; + if (state->source != RCAR_DU_PLANE_MEMORY) + ddcr4 |= PnDDCR4_VSPS; + rcar_du_plane_write(rgrp, index, PnDDCR4, ddcr4); +} + +static void rcar_du_plane_setup_format_gen3(struct rcar_du_group *rgrp, + unsigned int index, + const struct rcar_du_plane_state *state) +{ + rcar_du_plane_write(rgrp, index, PnMR, + PnMR_SPIM_TP_OFF | state->format->pnmr); + + rcar_du_plane_write(rgrp, index, PnDDCR4, + state->format->edf | PnDDCR4_CODE); +} + +static void rcar_du_plane_setup_format(struct rcar_du_group *rgrp, + unsigned int index, + const struct rcar_du_plane_state *state) +{ + struct rcar_du_device *rcdu = rgrp->dev; + + if (rcdu->info->gen < 3) + rcar_du_plane_setup_format_gen2(rgrp, index, state); + else + rcar_du_plane_setup_format_gen3(rgrp, index, state); /* Destination position and size */ - rcar_du_plane_write(rgrp, index, PnDSXR, plane->plane.state->crtc_w); - rcar_du_plane_write(rgrp, index, PnDSYR, plane->plane.state->crtc_h); - rcar_du_plane_write(rgrp, index, PnDPXR, plane->plane.state->crtc_x); - rcar_du_plane_write(rgrp, index, PnDPYR, plane->plane.state->crtc_y); - - /* Wrap-around and blinking, disabled */ - rcar_du_plane_write(rgrp, index, PnWASPR, 0); - rcar_du_plane_write(rgrp, index, PnWAMWR, 4095); - rcar_du_plane_write(rgrp, index, PnBTR, 0); - rcar_du_plane_write(rgrp, index, PnMLR, 0); + rcar_du_plane_write(rgrp, index, PnDSXR, state->state.crtc_w); + rcar_du_plane_write(rgrp, index, PnDSYR, state->state.crtc_h); + rcar_du_plane_write(rgrp, index, PnDPXR, state->state.crtc_x); + rcar_du_plane_write(rgrp, index, PnDPYR, state->state.crtc_y); + + if (rcdu->info->gen < 3) { + /* Wrap-around and blinking, disabled */ + rcar_du_plane_write(rgrp, index, PnWASPR, 0); + rcar_du_plane_write(rgrp, index, PnWAMWR, 4095); + rcar_du_plane_write(rgrp, index, PnBTR, 0); + rcar_du_plane_write(rgrp, index, PnMLR, 0); + } } -void rcar_du_plane_setup(struct rcar_du_plane *plane) +void __rcar_du_plane_setup(struct rcar_du_group *rgrp, + const struct rcar_du_plane_state *state) { - struct rcar_du_plane_state *state = - to_rcar_plane_state(plane->plane.state); + struct rcar_du_device *rcdu = rgrp->dev; - __rcar_du_plane_setup(plane, state->hwindex); + rcar_du_plane_setup_format(rgrp, state->hwindex, state); if (state->format->planes == 2) - __rcar_du_plane_setup(plane, (state->hwindex + 1) % 8); + rcar_du_plane_setup_format(rgrp, (state->hwindex + 1) % 8, + state); - rcar_du_plane_setup_fb(plane); + if (rcdu->info->gen < 3) + rcar_du_plane_setup_scanout(rgrp, state); + + if (state->source == RCAR_DU_PLANE_VSPD1) { + unsigned int vspd1_sink = rgrp->index ? 2 : 0; + + if (rcdu->vspd1_sink != vspd1_sink) { + rcdu->vspd1_sink = vspd1_sink; + rcar_du_set_dpad0_vsp1_routing(rcdu); + } + } } static int rcar_du_plane_atomic_check(struct drm_plane *plane, @@ -263,9 +585,27 @@ static void rcar_du_plane_atomic_update(struct drm_plane *plane, struct drm_plane_state *old_state) { struct rcar_du_plane *rplane = to_rcar_plane(plane); + struct rcar_du_plane_state *old_rstate; + struct rcar_du_plane_state *new_rstate; + + if (!plane->state->crtc) + return; + + rcar_du_plane_setup(rplane); + + /* Check whether the source has changed from memory to live source or + * from live source to memory. The source has been configured by the + * VSPS bit in the PnDDCR4 register. Although the datasheet states that + * the bit is updated during vertical blanking, it seems that updates + * only occur when the DU group is held in reset through the DSYSR.DRES + * bit. We thus need to restart the group if the source changes. + */ + old_rstate = to_rcar_plane_state(old_state); + new_rstate = to_rcar_plane_state(plane->state); - if (plane->state->crtc) - rcar_du_plane_setup(rplane); + if ((old_rstate->source == RCAR_DU_PLANE_MEMORY) != + (new_rstate->source == RCAR_DU_PLANE_MEMORY)) + rplane->group->need_restart = true; } static const struct drm_plane_helper_funcs rcar_du_plane_helper_funcs = { @@ -313,6 +653,7 @@ static void rcar_du_plane_reset(struct drm_plane *plane) return; state->hwindex = -1; + state->source = RCAR_DU_PLANE_MEMORY; state->alpha = 255; state->colorkey = RCAR_DU_COLORKEY_NONE; state->zpos = plane->type == DRM_PLANE_TYPE_PRIMARY ? 0 : 1; |