Commit ab334e13 authored by Laurent Pinchart's avatar Laurent Pinchart

drm: rcar-du: Move plane allocator to rcar_du_plane.c

The plane allocator is specific to DU planes and won't be used for
VSP-based planes, move it with the rest of the DU planes code where it
belongs.
Signed-off-by: default avatarLaurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
parent 2af03944
......@@ -192,290 +192,16 @@ static void rcar_du_output_poll_changed(struct drm_device *dev)
* Atomic Check and Update
*/
/*
* 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;
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)
{
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;
}
static int rcar_du_atomic_check(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;
int ret;
ret = drm_atomic_helper_check(dev, state);
if (ret < 0)
return ret;
/* 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,%u)\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,%u) 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,%u) 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,%u)\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;
return rcar_du_atomic_check_planes(dev, state);
}
struct rcar_du_commit {
......
......@@ -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>
......@@ -25,6 +26,291 @@
#include "rcar_du_plane.h"
#include "rcar_du_regs.h"
/* -----------------------------------------------------------------------------
* 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;
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)
{
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,%u)\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,%u) 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,%u) 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,%u)\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)
......
......@@ -71,6 +71,9 @@ to_rcar_plane_state(struct drm_plane_state *state)
return container_of(state, struct rcar_du_plane_state, state);
}
int rcar_du_atomic_check_planes(struct drm_device *dev,
struct drm_atomic_state *state);
int rcar_du_planes_init(struct rcar_du_group *rgrp);
void rcar_du_plane_setup(struct rcar_du_plane *plane);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment