In the case where we need to support wide-display using more than one overlay to achieve the needed display resolution, we need to be able to dynamically assign overlays to planes instead of having the overlays being statically mapped to planes. This also means that on occasion where the number of requested planes exceeds the numbers of overlays required to display them then a failure would be returned for the plane that cannot be handled at that time. It is up to user space to make sure the H/W resource are not over-subscribed. Signed-off-by: Benoit Parrot <bparrot@xxxxxx> --- drivers/gpu/drm/omapdrm/Makefile | 1 + drivers/gpu/drm/omapdrm/omap_drv.c | 119 ++++++++++- drivers/gpu/drm/omapdrm/omap_drv.h | 15 +- drivers/gpu/drm/omapdrm/omap_fb.c | 33 ++- drivers/gpu/drm/omapdrm/omap_fb.h | 4 +- drivers/gpu/drm/omapdrm/omap_overlay.c | 366 +++++++++++++++++++++++++++++++++ drivers/gpu/drm/omapdrm/omap_overlay.h | 80 +++++++ drivers/gpu/drm/omapdrm/omap_plane.c | 322 ++++++++++++++++++++++++----- drivers/gpu/drm/omapdrm/omap_plane.h | 20 ++ 9 files changed, 892 insertions(+), 68 deletions(-) create mode 100644 drivers/gpu/drm/omapdrm/omap_overlay.c create mode 100644 drivers/gpu/drm/omapdrm/omap_overlay.h diff --git a/drivers/gpu/drm/omapdrm/Makefile b/drivers/gpu/drm/omapdrm/Makefile index f115253115c5..800dfd035360 100644 --- a/drivers/gpu/drm/omapdrm/Makefile +++ b/drivers/gpu/drm/omapdrm/Makefile @@ -12,6 +12,7 @@ omapdrm-y := omap_drv.o \ omap_debugfs.o \ omap_crtc.o \ omap_plane.o \ + omap_overlay.o \ omap_encoder.o \ omap_connector.o \ omap_fb.o \ diff --git a/drivers/gpu/drm/omapdrm/omap_drv.c b/drivers/gpu/drm/omapdrm/omap_drv.c index ef3b0e3571ec..f0a5c3dab471 100644 --- a/drivers/gpu/drm/omapdrm/omap_drv.c +++ b/drivers/gpu/drm/omapdrm/omap_drv.c @@ -16,11 +16,7 @@ */ #include <linux/sys_soc.h> - -#include <drm/drm_atomic.h> -#include <drm/drm_atomic_helper.h> -#include <drm/drm_crtc_helper.h> -#include <drm/drm_fb_helper.h> +#include <linux/sort.h> #include "omap_dmm_tiler.h" #include "omap_drv.h" @@ -113,9 +109,100 @@ static void omap_atomic_commit_tail(struct drm_atomic_state *old_state) drm_atomic_helper_cleanup_planes(dev, old_state); + omap_overlay_disable_unassigned(old_state); + priv->dispc_ops->runtime_put(priv->dispc); } +static int drm_atomic_state_normalized_zpos_cmp(const void *a, const void *b) +{ + const struct drm_plane_state *sa = *(struct drm_plane_state **)a; + const struct drm_plane_state *sb = *(struct drm_plane_state **)b; + + if (sa->normalized_zpos != sb->normalized_zpos) + return sa->normalized_zpos - sb->normalized_zpos; + else + return sa->plane->base.id - sb->plane->base.id; +} + +static int omap_atomic_update_normalize_zpos(struct drm_device *dev, + struct drm_atomic_state *state) +{ + struct drm_crtc *crtc; + struct drm_crtc_state *old_state, *new_state; + struct drm_plane *plane; + int i, n, inc; + int total_planes = dev->mode_config.num_total_plane; + struct drm_plane_state **states; + int ret = 0; + + states = kmalloc_array(total_planes, sizeof(*states), GFP_KERNEL); + if (!states) + return -ENOMEM; + + for_each_oldnew_crtc_in_state(state, crtc, old_state, new_state, i) { + if (old_state->plane_mask == new_state->plane_mask && + !new_state->zpos_changed) + continue; + + /* Reset plane increment and index value for every crtc */ + n = 0; + + /* + * Normalization process might create new states for planes + * which normalized_zpos has to be recalculated. + */ + drm_for_each_plane_mask(plane, dev, new_state->plane_mask) { + struct drm_plane_state *plane_state = + drm_atomic_get_plane_state(new_state->state, + plane); + if (IS_ERR(plane_state)) { + ret = PTR_ERR(plane_state); + goto done; + } + states[n++] = plane_state; + } + + sort(states, n, sizeof(*states), + drm_atomic_state_normalized_zpos_cmp, NULL); + + for (i = 0, inc = 0; i < n; i++) { + plane = states[i]->plane; + + states[i]->normalized_zpos = i + inc; + DRM_DEBUG_ATOMIC("[PLANE:%d:%s] updated normalized zpos value %d\n", + plane->base.id, plane->name, + states[i]->normalized_zpos); + + if (is_omap_plane_dual_overlay(states[i])) + inc++; + } + new_state->zpos_changed = true; + } + +done: + kfree(states); + return ret; +} + +static int omap_atomic_check(struct drm_device *dev, + struct drm_atomic_state *state) +{ + int ret; + + ret = drm_atomic_helper_check(dev, state); + if (ret) + return ret; + + if (dev->mode_config.normalize_zpos) { + ret = omap_atomic_update_normalize_zpos(dev, state); + if (ret) + return ret; + } + + return 0; +} + static const struct drm_mode_config_helper_funcs omap_mode_config_helper_funcs = { .atomic_commit_tail = omap_atomic_commit_tail, }; @@ -123,7 +210,7 @@ static const struct drm_mode_config_helper_funcs omap_mode_config_helper_funcs = static const struct drm_mode_config_funcs omap_mode_config_funcs = { .fb_create = omap_framebuffer_create, .output_poll_changed = drm_fb_helper_output_poll_changed, - .atomic_check = drm_atomic_helper_check, + .atomic_check = omap_atomic_check, .atomic_commit = drm_atomic_helper_commit, }; @@ -296,7 +383,7 @@ static int omap_modeset_init(struct drm_device *dev) return -EINVAL; plane = omap_plane_init(dev, plane_idx, DRM_PLANE_TYPE_OVERLAY, - plane_crtc_mask); + plane_crtc_mask); if (IS_ERR(plane)) return PTR_ERR(plane); @@ -560,10 +647,18 @@ static int omapdrm_init(struct omap_drm_private *priv, struct device *dev) omap_gem_init(ddev); + ret = omap_global_obj_init(priv); + if (ret) + goto err_free_drm_dev; + + ret = omap_hwoverlays_init(priv); + if (ret) + goto err_free_priv_obj; + ret = omap_modeset_init(ddev); if (ret) { dev_err(priv->dev, "omap_modeset_init failed: ret=%d\n", ret); - goto err_free_drm_dev; + goto err_free_overlays; } /* Initialize vblank handling, start with all CRTCs disabled. */ @@ -577,7 +672,6 @@ static int omapdrm_init(struct omap_drm_private *priv, struct device *dev) drm_crtc_vblank_off(priv->crtcs[i]); omap_fbdev_init(ddev); - drm_kms_helper_poll_init(ddev); omap_modeset_enable_external_hpd(); @@ -599,6 +693,10 @@ static int omapdrm_init(struct omap_drm_private *priv, struct device *dev) err_cleanup_modeset: drm_mode_config_cleanup(ddev); omap_drm_irq_uninstall(ddev); +err_free_overlays: + omap_hwoverlays_destroy(priv); +err_free_priv_obj: + omap_global_obj_fini(priv); err_free_drm_dev: omap_gem_deinit(ddev); drm_dev_unref(ddev); @@ -632,6 +730,9 @@ static void omapdrm_cleanup(struct omap_drm_private *priv) drm_dev_unref(ddev); + omap_hwoverlays_destroy(priv); + omap_global_obj_fini(priv); + destroy_workqueue(priv->wq); omap_disconnect_dssdevs(); diff --git a/drivers/gpu/drm/omapdrm/omap_drv.h b/drivers/gpu/drm/omapdrm/omap_drv.h index 6eaee4df4559..eb71ea8decee 100644 --- a/drivers/gpu/drm/omapdrm/omap_drv.h +++ b/drivers/gpu/drm/omapdrm/omap_drv.h @@ -23,7 +23,10 @@ #include <linux/workqueue.h> #include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> #include <drm/drm_gem.h> #include <drm/omap_drm.h> @@ -37,6 +40,7 @@ #include "omap_gem.h" #include "omap_irq.h" #include "omap_plane.h" +#include "omap_overlay.h" #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) #define VERB(fmt, ...) if (0) DRM_DEBUG(fmt, ##__VA_ARGS__) /* verbose debug */ @@ -66,6 +70,16 @@ struct omap_drm_private { unsigned int num_connectors; struct drm_connector *connectors[8]; + unsigned int num_ovls; + struct omap_hw_overlay *overlays[8]; + + /* + * Global private object state, Do not access directly, use + * omap_global_get_state() + */ + struct drm_modeset_lock glob_state_lock; + struct drm_private_obj glob_state; + struct drm_fb_helper *fbdev; struct workqueue_struct *wq; @@ -91,7 +105,6 @@ struct omap_drm_private { unsigned int max_bandwidth; }; - int omap_debugfs_init(struct drm_minor *minor); #endif /* __OMAPDRM_DRV_H__ */ diff --git a/drivers/gpu/drm/omapdrm/omap_fb.c b/drivers/gpu/drm/omapdrm/omap_fb.c index 5fd22ca73913..b9573c163117 100644 --- a/drivers/gpu/drm/omapdrm/omap_fb.c +++ b/drivers/gpu/drm/omapdrm/omap_fb.c @@ -153,7 +153,9 @@ static u32 drm_rotation_to_tiler(unsigned int drm_rot) /* update ovl info for scanout, handles cases of multi-planar fb's, etc. */ void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, - struct drm_plane_state *state, struct omap_overlay_info *info) + struct drm_plane_state *state, + struct omap_overlay_info *info, + struct omap_overlay_info *r_info) { struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); const struct drm_format_info *format = omap_fb->format; @@ -206,7 +208,8 @@ void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, info->rotation_type = OMAP_DSS_ROT_TILER; info->rotation = state->rotation ?: DRM_MODE_ROTATE_0; /* Note: stride in TILER units, not pixels */ - info->screen_width = omap_gem_tiled_stride(plane->bo, orient); + info->screen_width = + omap_gem_tiled_stride(plane->bo, orient); } else { switch (state->rotation & DRM_MODE_ROTATE_MASK) { case 0: @@ -221,10 +224,10 @@ void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, break; } - info->paddr = get_linear_addr(plane, format, 0, x, y); + info->paddr = get_linear_addr(plane, format, 0, x, y); info->rotation_type = OMAP_DSS_ROT_NONE; - info->rotation = DRM_MODE_ROTATE_0; - info->screen_width = plane->pitch; + info->rotation = DRM_MODE_ROTATE_0; + info->screen_width = plane->pitch; } /* convert to pixels: */ @@ -238,11 +241,29 @@ void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, omap_gem_rotated_dma_addr(plane->bo, orient, x/2, y/2, &info->p_uv_addr); } else { - info->p_uv_addr = get_linear_addr(plane, format, 1, x, y); + info->p_uv_addr = + get_linear_addr(plane, format, 1, x, y); } } else { info->p_uv_addr = 0; } + + if (r_info) { + info->width /= 2; + info->out_width /= 2; + + *r_info = *info; + + r_info->pos_x = info->pos_x + info->out_width; + + r_info->paddr = get_linear_addr(&omap_fb->planes[0], format, 0, + x + info->width, y); + if (fb->format->format == DRM_FORMAT_NV12) { + r_info->p_uv_addr = + get_linear_addr(&omap_fb->planes[1], format, 1, + x + info->width, y); + } + } } /* pin, prepare for scanout: */ diff --git a/drivers/gpu/drm/omapdrm/omap_fb.h b/drivers/gpu/drm/omapdrm/omap_fb.h index 94ad5f9e4404..8c116c1aac0d 100644 --- a/drivers/gpu/drm/omapdrm/omap_fb.h +++ b/drivers/gpu/drm/omapdrm/omap_fb.h @@ -37,7 +37,9 @@ struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, int omap_framebuffer_pin(struct drm_framebuffer *fb); void omap_framebuffer_unpin(struct drm_framebuffer *fb); void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, - struct drm_plane_state *state, struct omap_overlay_info *info); + struct drm_plane_state *state, + struct omap_overlay_info *info, + struct omap_overlay_info *r_info); struct drm_connector *omap_framebuffer_get_next_connector( struct drm_framebuffer *fb, struct drm_connector *from); bool omap_framebuffer_supports_rotation(struct drm_framebuffer *fb); diff --git a/drivers/gpu/drm/omapdrm/omap_overlay.c b/drivers/gpu/drm/omapdrm/omap_overlay.c new file mode 100644 index 000000000000..0a1327c31b69 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_overlay.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Benoit Parrot, <bparrot@xxxxxx> + */ + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_plane_helper.h> + +#include "omap_dmm_tiler.h" +#include "omap_drv.h" + +/* + * overlay funcs + */ +static void __maybe_unused +omap_overlay_atomic_print_state(struct drm_printer *p, + const struct omap_global_state *state, + struct omap_drm_private *priv) +{ + int i; + + drm_printf(p, "\tomap_global_state=%p\n", state); + if (state) { + for (i = 0; i < priv->num_ovls; i++) { + struct drm_plane *plane = + state->overlay.hwoverlay_to_plane[i]; + + drm_printf(p, "\t\t[%d] plane=%p\n", i, plane); + if (plane) + drm_printf(p, "\t\t\t plane=%s\n", plane->name); + } + } +} + +/* Global/shared object state funcs */ + +/* + * This is a helper that returns the private state currently in operation. + * Note that this would return the "old_state" if called in the atomic check + * path, and the "new_state" after the atomic swap has been done. + */ +static struct omap_global_state * +omap_get_existing_global_state(struct omap_drm_private *priv) +{ + return to_omap_global_state(priv->glob_state.state); +} + +/* + * This acquires the modeset lock set aside for global state, creates + * a new duplicated private object state. + */ +static struct omap_global_state *__must_check +omap_get_global_state(struct drm_atomic_state *s) +{ + struct omap_drm_private *priv = s->dev->dev_private; + struct drm_private_state *priv_state; + int ret; + + while (1) { + ret = drm_modeset_lock(&priv->glob_state_lock, s->acquire_ctx); + if (ret != -EDEADLK) + break; + + drm_modeset_backoff(s->acquire_ctx); + } + + if (ret) + return ERR_PTR(ret); + + priv_state = drm_atomic_get_private_obj_state(s, &priv->glob_state); + if (IS_ERR(priv_state)) + return ERR_CAST(priv_state); + + return to_omap_global_state(priv_state); +} + +static struct drm_private_state * +omap_global_duplicate_state(struct drm_private_obj *obj) +{ + struct omap_global_state *state; + + state = kmemdup(obj->state, sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base); + + return &state->base; +} + +static void omap_global_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + struct omap_global_state *omap_state = to_omap_global_state(state); + + kfree(omap_state); +} + +static const struct drm_private_state_funcs omap_global_state_funcs = { + .atomic_duplicate_state = omap_global_duplicate_state, + .atomic_destroy_state = omap_global_destroy_state, +}; + +int omap_global_obj_init(struct omap_drm_private *priv) +{ + struct omap_global_state *state; + + drm_modeset_lock_init(&priv->glob_state_lock); + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + drm_atomic_private_obj_init(&priv->glob_state, + &state->base, + &omap_global_state_funcs); + return 0; +} + +void omap_global_obj_fini(struct omap_drm_private *priv) +{ + drm_atomic_private_obj_fini(&priv->glob_state); + drm_modeset_lock_fini(&priv->glob_state_lock); +} + +static struct omap_hw_overlay * +omap_plane_find_free_overlay(struct drm_device *dev, + struct omap_hw_overlay_state *new_state, + u32 caps, u32 fourcc, u32 crtc_mask) +{ + struct omap_drm_private *priv = dev->dev_private; + const struct dispc_ops *ops = priv->dispc_ops; + int i; + + DBG("caps: %x fourcc: %x crtc: %x\n", caps, fourcc, crtc_mask); + + for (i = 0; i < priv->num_ovls; i++) { + struct omap_hw_overlay *cur = priv->overlays[i]; + + DBG("%d: id: %d cur->caps: %x cur->crtc: %x\n", + cur->idx, cur->overlay_id, cur->caps, cur->possible_crtcs); + + /* skip if already in-use */ + if (new_state->hwoverlay_to_plane[cur->idx]) + continue; + + /* check if allowed on crtc */ + if (!(cur->possible_crtcs & crtc_mask)) + continue; + + /* skip if doesn't support some required caps: */ + if (caps & ~cur->caps) + continue; + + /* check supported format */ + if (!ops->ovl_color_mode_supported(priv->dispc, + cur->overlay_id, + fourcc)) + continue; + + return cur; + } + + DBG("no match\n"); + return NULL; +} + +int omap_overlay_assign(struct drm_atomic_state *s, struct drm_plane *plane, + u32 caps, u32 fourcc, u32 crtc_mask, + struct omap_hw_overlay **overlay, + struct omap_hw_overlay **r_overlay) +{ + struct omap_drm_private *priv = s->dev->dev_private; + struct omap_global_state *new_global_state, *old_global_state; + struct omap_hw_overlay_state *old_state, *new_state; + struct omap_hw_overlay *ovl, *r_ovl; + + new_global_state = omap_get_global_state(s); + if (IS_ERR(new_global_state)) + return PTR_ERR(new_global_state); + + /* + * grab old_state after omap_get_global_state(), + * since now we hold lock: + */ + old_global_state = omap_get_existing_global_state(priv); + DBG("new_global_state: %p old_global_state: %p should be different (%d)", + new_global_state, old_global_state, new_global_state != old_global_state); + + old_state = &old_global_state->overlay; + new_state = &new_global_state->overlay; + + if (!*overlay) { + ovl = omap_plane_find_free_overlay(s->dev, new_state, + caps, fourcc, crtc_mask); + if (!ovl) + return -ENOMEM; + + new_state->hwoverlay_to_plane[ovl->idx] = plane; + *overlay = ovl; + + if (r_overlay) { + r_ovl = omap_plane_find_free_overlay(s->dev, new_state, + caps, fourcc, + crtc_mask); + if (!r_ovl) { + new_state->hwoverlay_to_plane[ovl->idx] = NULL; + *overlay = NULL; + return -ENOMEM; + } + + new_state->hwoverlay_to_plane[r_ovl->idx] = plane; + *r_overlay = r_ovl; + } + + + DBG("%s: assign to plane %s for caps %x", + (*overlay)->name, plane->name, caps); + + if (r_overlay) { + DBG("%s: assign to right of plane %s for caps %x", + (*r_overlay)->name, plane->name, caps); + } + } + + return 0; +} + +void omap_overlay_release(struct drm_atomic_state *s, + struct omap_hw_overlay *overlay) +{ + struct omap_global_state *state = omap_get_global_state(s); + struct omap_hw_overlay_state *new_state = &state->overlay; + + if (!overlay) + return; + + if (WARN_ON(!new_state->hwoverlay_to_plane[overlay->idx])) + return; + + DBG("%s: release from plane %s", overlay->name, + new_state->hwoverlay_to_plane[overlay->idx]->name); + + new_state->hwoverlay_to_plane[overlay->idx] = NULL; +} + +/* + * This is called only from omap_atomic_commit_tail() + * as a cleanup step to make sure hw overlay which are no longer + * are disabled. + * + * I was originally taking the glob_state_lock here by calling + * omap_get_global_state(s) but doing so here was causing all kind + * lock related warnings, for instance: + * WARNING: CPU: 0 PID: 68 at drivers/gpu/drm/drm_modeset_lock.c:241 + * and + * WARNING: CPU: 0 PID: 68 at drivers/gpu/drm/drm_modeset_lock.c:244 + * As well as also generating these: + * ================================== + * WARNING: Nested lock was not taken + * 4.18.0-rc2-00055-g5d51e5159b0a #24 Tainted: G W + * ---------------------------------- + * kworker/u2:3/66 is trying to lock: + * 51abea2e (crtc_ww_class_mutex){+.+.}, at: drm_modeset_lock+0xd0/0x140 + * + * but this task is not holding: + * �21 + * + * The only thing that worked so far was to stop trying to take that lock + * in this particular case. It might the real solution but I would like + * to sure. + */ +void omap_overlay_disable_unassigned(struct drm_atomic_state *s) +{ + struct omap_drm_private *priv = s->dev->dev_private; + struct omap_hw_overlay_state *new_state; + struct omap_global_state *old_state; + int i; + + old_state = omap_get_existing_global_state(priv); + new_state = &old_state->overlay; + + for (i = 0; i < priv->num_ovls; i++) { + struct omap_hw_overlay *cur = priv->overlays[i]; + + if (!new_state->hwoverlay_to_plane[cur->idx]) { + priv->dispc_ops->ovl_enable(priv->dispc, + cur->overlay_id, + false); + + /* + * Since we are disabling this overlay in this + * atomic cycle we can reset the avalaible crtcs + * it can be used on + */ + cur->possible_crtcs = (1 << priv->num_crtcs) - 1; + } + } +} + +void omap_overlay_destroy(struct omap_hw_overlay *overlay) +{ + kfree(overlay); +} + +static struct omap_hw_overlay *omap_overlay_init(enum omap_plane_id overlay_id, + enum omap_overlay_caps caps) +{ + struct omap_hw_overlay *overlay; + + overlay = kzalloc(sizeof(*overlay), GFP_KERNEL); + if (!overlay) + return ERR_PTR(-ENOMEM); + + overlay->name = overlay2name(overlay_id); + overlay->overlay_id = overlay_id; + overlay->caps = caps; + /* + * When this is called priv->num_crtcs is not known yet. + * Use a safe mask value to start with, it will get updated to the + * proper value after the first use. + */ + overlay->possible_crtcs = 0xff; + + return overlay; +} + +int omap_hwoverlays_init(struct omap_drm_private *priv) +{ + static const enum omap_plane_id overlays[] = { + OMAP_DSS_GFX, OMAP_DSS_VIDEO1, + OMAP_DSS_VIDEO2, OMAP_DSS_VIDEO3, + }; + u32 num_overlays = priv->dispc_ops->get_num_ovls(priv->dispc); + enum omap_overlay_caps caps; + int i, ret; + + for (i = 0; i < num_overlays; i++) { + struct omap_hw_overlay *overlay; + + caps = priv->dispc_ops->ovl_get_caps(priv->dispc, overlays[i]); + overlay = omap_overlay_init(overlays[i], caps); + if (IS_ERR(overlay)) { + ret = PTR_ERR(overlay); + dev_err(priv->dev, "failed to construct overlay for %s (%d)\n", + overlay2name(i), ret); + return ret; + } + overlay->idx = priv->num_ovls; + priv->overlays[priv->num_ovls++] = overlay; + } + + return 0; +} + +void omap_hwoverlays_destroy(struct omap_drm_private *priv) +{ + int i; + + for (i = 0; i < priv->num_ovls; i++) { + omap_overlay_destroy(priv->overlays[i]); + priv->overlays[i] = NULL; + } +} diff --git a/drivers/gpu/drm/omapdrm/omap_overlay.h b/drivers/gpu/drm/omapdrm/omap_overlay.h new file mode 100644 index 000000000000..ed94a260ba10 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_overlay.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Benoit Parrot, <bparrot@xxxxxx> + */ + +#ifndef __OMAPDRM_OVERLAY_H__ +#define __OMAPDRM_OVERLAY_H__ + +#include <linux/types.h> + +enum drm_plane_type; + +struct drm_device; +struct drm_mode_object; +struct drm_plane; + +/* Used to associate a HW overlay/plane to a plane */ +struct omap_hw_overlay { + int idx; + + const char *name; + enum omap_plane_id overlay_id; + + enum omap_overlay_caps caps; + /* + * The CRTC(s) this overlay is currently allowed on. + * When the overlay is unused and was not assigned to any crtc then + * this will be the equal to the plane possible_crtcs otherwise it + * will be the current crtc this overlay is displayed on. + * When clearing the overlay to plane assignemnt while going through + * an atomic_check sequence we need to remember which crtc the overlay + * was on as we do not want to create flicker. We want to be able to + * reassign the overlay to the same crtc it was previously on. + */ + u32 possible_crtcs; + /* Reference to the associated drm_plane */ + struct drm_plane *plane; +}; + +/* global atomic state of assignment between pipes and planes: */ +struct omap_hw_overlay_state { + struct drm_plane *hwoverlay_to_plane[8]; +}; + +/* Global private object state for tracking resources that are shared across + * multiple kms objects (planes/crtcs/etc). + */ +#define to_omap_global_state(x) container_of(x, struct omap_global_state, base) +struct omap_global_state { + struct drm_private_state base; + + struct drm_atomic_state *state; + + struct omap_hw_overlay_state overlay; +}; + +static inline const char *overlay2name(enum omap_plane_id id) +{ + static const char *name[] = { + [OMAP_DSS_GFX] = "gfx", + [OMAP_DSS_VIDEO1] = "vid1", + [OMAP_DSS_VIDEO2] = "vid2", + [OMAP_DSS_VIDEO3] = "vid3", + }; + return name[id]; +} + +int omap_hwoverlays_init(struct omap_drm_private *priv); +void omap_global_obj_fini(struct omap_drm_private *priv); +void omap_hwoverlays_destroy(struct omap_drm_private *priv); +int omap_global_obj_init(struct omap_drm_private *priv); +int omap_overlay_assign(struct drm_atomic_state *s, struct drm_plane *plane, + u32 caps, u32 fourcc, u32 crtc_mask, + struct omap_hw_overlay **overlay, + struct omap_hw_overlay **r_overlay); +void omap_overlay_release(struct drm_atomic_state *s, struct omap_hw_overlay *overlay); +void omap_overlay_disable_unassigned(struct drm_atomic_state *s); + +#endif /* __OMAPDRM_OVERLAY_H__ */ diff --git a/drivers/gpu/drm/omapdrm/omap_plane.c b/drivers/gpu/drm/omapdrm/omap_plane.c index 161233cbc9a0..01f1da155994 100644 --- a/drivers/gpu/drm/omapdrm/omap_plane.c +++ b/drivers/gpu/drm/omapdrm/omap_plane.c @@ -30,10 +30,12 @@ struct omap_plane { struct drm_plane base; - enum omap_plane_id id; + enum omap_plane_id default_id; const char *name; }; +static const char *plane_id_to_name[]; + static int omap_plane_prepare_fb(struct drm_plane *plane, struct drm_plane_state *new_state) { @@ -50,15 +52,26 @@ static void omap_plane_cleanup_fb(struct drm_plane *plane, omap_framebuffer_unpin(old_state->fb); } +static bool plane_enabled(struct drm_plane_state *state) +{ + return state->visible; +} + static void omap_plane_atomic_update(struct drm_plane *plane, struct drm_plane_state *old_state) { struct omap_drm_private *priv = plane->dev->dev_private; struct omap_plane *omap_plane = to_omap_plane(plane); struct drm_plane_state *state = plane->state; - struct omap_overlay_info info; + struct omap_plane_state *omap_state = to_omap_plane_state(state); + struct omap_overlay_info info, r_info; + enum omap_plane_id ovl_id, r_ovl_id; int ret; + bool dual_plane = !!omap_state->r_overlay; + ovl_id = omap_state->overlay->overlay_id; + DBG("[PLANE:%d:%s] overlay_id: %d\n", plane->base.id, plane->name, + ovl_id); DBG("%s, crtc=%p fb=%p", omap_plane->name, state->crtc, state->fb); memset(&info, 0, sizeof(info)); @@ -67,75 +80,231 @@ static void omap_plane_atomic_update(struct drm_plane *plane, info.global_alpha = 0xff; info.zorder = state->normalized_zpos; + r_info = info; + /* update scanout: */ - omap_framebuffer_update_scanout(state->fb, state, &info); + omap_framebuffer_update_scanout(state->fb, state, &info, + dual_plane ? &r_info : NULL); - DBG("%dx%d -> %dx%d (%d)", info.width, info.height, - info.out_width, info.out_height, - info.screen_width); + DBG("%s: %dx%d -> %dx%d (%d)", + overlay2name(ovl_id), info.width, info.height, + info.out_width, info.out_height, info.screen_width); DBG("%d,%d %pad %pad", info.pos_x, info.pos_y, - &info.paddr, &info.p_uv_addr); + &info.paddr, &info.p_uv_addr); + + if (dual_plane) { + r_ovl_id = omap_state->r_overlay->overlay_id; + /* + * If the current plane uses 2 hw planes the very next + * zorder is used by the r_overlay so we just use the + * main overlay zorder + 1 + */ + r_info.zorder = info.zorder + 1; + + DBG("%s: %dx%d -> %dx%d (%d)", + overlay2name(r_ovl_id), r_info.width, r_info.height, + r_info.out_width, r_info.out_height, r_info.screen_width); + DBG("%d,%d %pad %pad", r_info.pos_x, r_info.pos_y, + &r_info.paddr, &r_info.p_uv_addr); + } /* and finally, update omapdss: */ - ret = priv->dispc_ops->ovl_setup(priv->dispc, omap_plane->id, &info, + ret = priv->dispc_ops->ovl_setup(priv->dispc, ovl_id, &info, omap_crtc_timings(state->crtc), false, omap_crtc_channel(state->crtc)); if (ret) { - dev_err(plane->dev->dev, "Failed to setup plane %s\n", + dev_err(plane->dev->dev, "Failed to setup plane1 %s\n", omap_plane->name); - priv->dispc_ops->ovl_enable(priv->dispc, omap_plane->id, false); + priv->dispc_ops->ovl_enable(priv->dispc, ovl_id, false); return; } - priv->dispc_ops->ovl_enable(priv->dispc, omap_plane->id, true); + priv->dispc_ops->ovl_enable(priv->dispc, ovl_id, true); + + if (dual_plane) { + ret = priv->dispc_ops->ovl_setup(priv->dispc, r_ovl_id, &r_info, + omap_crtc_timings(state->crtc), false, + omap_crtc_channel(state->crtc)); + if (ret) { + dev_err(plane->dev->dev, "Failed to setup plane2 %s\n", + omap_plane->name); + priv->dispc_ops->ovl_enable(priv->dispc, r_ovl_id, false); + priv->dispc_ops->ovl_enable(priv->dispc, ovl_id, false); + return; + } + + priv->dispc_ops->ovl_enable(priv->dispc, r_ovl_id, true); + } } static void omap_plane_atomic_disable(struct drm_plane *plane, struct drm_plane_state *old_state) { struct omap_drm_private *priv = plane->dev->dev_private; - struct omap_plane *omap_plane = to_omap_plane(plane); + struct omap_plane_state *omap_state = to_omap_plane_state(old_state); + bool dual_plane = !!omap_state->r_overlay; + + DBG("%s: check (overlay %p r_overlay %p)", plane->name, + omap_state->overlay, omap_state->r_overlay); + + if (!omap_state->overlay) + return; plane->state->rotation = DRM_MODE_ROTATE_0; plane->state->zpos = plane->type == DRM_PLANE_TYPE_PRIMARY - ? 0 : omap_plane->id; - - priv->dispc_ops->ovl_enable(priv->dispc, omap_plane->id, false); + ? 0 : omap_state->overlay->overlay_id; + + priv->dispc_ops->ovl_enable(priv->dispc, omap_state->overlay->overlay_id, false); + omap_overlay_release(old_state->state, omap_state->overlay); + omap_state->overlay = NULL; + if (dual_plane) { + priv->dispc_ops->ovl_enable(priv->dispc, omap_state->r_overlay->overlay_id, false); + omap_overlay_release(old_state->state, omap_state->r_overlay); + omap_state->r_overlay = NULL; + } } +#define FRAC_16_16(mult, div) (((mult) << 16) / (div)) static int omap_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) { + struct omap_drm_private *priv = plane->dev->dev_private; + struct drm_crtc *crtc; struct drm_crtc_state *crtc_state; + struct drm_plane_state *old_state = plane->state; + struct omap_plane_state *omap_state = to_omap_plane_state(state); + const struct dispc_ops *ops = priv->dispc_ops; + u16 width, height; + u32 crtc_mask; + u32 fourcc; + u32 caps = 0; + bool new_hw_overlay = false; + bool new_r_hw_overlay = false; + bool out_of_bounds = false; + int min_scale, max_scale; + u32 max_width, max_height; + int ret; - if (!state->fb) - return 0; + DBG("%s: check (%d -> %d)", plane->name, + plane_enabled(old_state), plane_enabled(state)); + + priv->dispc_ops->ovl_get_max_size(priv->dispc, &width, &height); + max_width = width << 16; + max_height = height << 16; - /* crtc should only be NULL when disabling (i.e., !state->fb) */ - if (WARN_ON(!state->crtc)) + crtc = state->crtc ? state->crtc : plane->state->crtc; + if (!crtc) return 0; - crtc_state = drm_atomic_get_existing_crtc_state(state->state, state->crtc); + crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc); /* we should have a crtc state if the plane is attached to a crtc */ if (WARN_ON(!crtc_state)) return 0; - if (!crtc_state->enable) - return 0; + /* Make sure source dimensions are within bounds. */ + if (state->src_h > max_height) + out_of_bounds = true; - if (state->crtc_x < 0 || state->crtc_y < 0) - return -EINVAL; + if (state->src_w > max_width) { + if (state->src_w <= (2 * max_width)) + new_r_hw_overlay = true; + else + out_of_bounds = true; + } - if (state->crtc_x + state->crtc_w > crtc_state->adjusted_mode.hdisplay) - return -EINVAL; + if (out_of_bounds) { + struct drm_rect src = drm_plane_state_src(state); + DBG("Invalid source size "DRM_RECT_FP_FMT, + DRM_RECT_FP_ARG(&src)); + return -ERANGE; + } - if (state->crtc_y + state->crtc_h > crtc_state->adjusted_mode.vdisplay) - return -EINVAL; + min_scale = FRAC_16_16(1, 4); + max_scale = FRAC_16_16(8, 1); + + ret = drm_atomic_helper_check_plane_state(state, crtc_state, + min_scale, max_scale, + true, true); + if (ret) + return ret; if (state->rotation != DRM_MODE_ROTATE_0 && !omap_framebuffer_supports_rotation(state->fb)) return -EINVAL; + if (plane_enabled(state)) { + if ((state->src_w >> 16) != state->crtc_w || + (state->src_h >> 16) != state->crtc_h) + caps |= OMAP_DSS_OVL_CAP_SCALE; + + fourcc = state->fb->format->format; + crtc_mask = drm_crtc_mask(state->crtc); + + /* (re)allocate hw overlay if we don't have one or caps-mismatch: */ + if (!omap_state->overlay || (caps & ~omap_state->overlay->caps)) { + new_hw_overlay = true; + } else { + /* check if allowed on crtc */ + if (!(omap_state->overlay->possible_crtcs & crtc_mask)) + new_hw_overlay = true; + + /* check supported format */ + if (!ops->ovl_color_mode_supported(priv->dispc, + omap_state->overlay->overlay_id, + fourcc)) + new_hw_overlay = true; + } + /* + * check if we need two overlays and only have 1 or + * if we had 2 overlays but will only need 1 + */ + if ((new_r_hw_overlay && !omap_state->r_overlay) || + (!new_r_hw_overlay && omap_state->r_overlay)) + new_hw_overlay = true; + + if (new_hw_overlay) { + struct omap_hw_overlay *old_ovl = + omap_state->overlay; + struct omap_hw_overlay *old_r_ovl = + omap_state->r_overlay; + struct omap_hw_overlay *new_ovl = NULL; + struct omap_hw_overlay *new_r_ovl = NULL; + + omap_overlay_release(state->state, old_ovl); + omap_overlay_release(state->state, old_r_ovl); + + ret = omap_overlay_assign(state->state, plane, caps, + fourcc, crtc_mask, &new_ovl, + new_r_hw_overlay ? + &new_r_ovl : NULL); + if (ret) { + DBG("%s: failed to assign hw_overlay(s)!", + plane->name); + omap_state->overlay = NULL; + omap_state->r_overlay = NULL; + return ret; + } + + omap_state->overlay = new_ovl; + if (new_r_hw_overlay) + omap_state->r_overlay = new_r_ovl; + else + omap_state->r_overlay = NULL; + } + } else { + omap_overlay_release(state->state, omap_state->overlay); + omap_overlay_release(state->state, omap_state->r_overlay); + omap_state->overlay = NULL; + omap_state->r_overlay = NULL; + } + + if (omap_state->overlay) + DBG("plane: %s overlay_id: %d", plane->name, + omap_state->overlay->overlay_id); + if (omap_state->r_overlay) + DBG("plane: %s r_overlay_id: %d", plane->name, + omap_state->r_overlay->overlay_id); + return 0; } @@ -182,20 +351,77 @@ void omap_plane_install_properties(struct drm_plane *plane, drm_object_attach_property(obj, priv->zorder_prop, 0); } +/* Add duplicate and destroy state helper */ +static struct drm_plane_state * +omap_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct omap_plane_state *state; + struct omap_plane_state *copy; + + if (WARN_ON(!plane->state)) + return NULL; + + state = to_omap_plane_state(plane->state); + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (copy == NULL) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, ©->base); + + return ©->base; +} + +static void omap_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + __drm_atomic_helper_plane_destroy_state(state); + kfree(to_omap_plane_state(state)); +} + static void omap_plane_reset(struct drm_plane *plane) { struct omap_plane *omap_plane = to_omap_plane(plane); + struct omap_plane_state *omap_state; + + if (plane->state) + omap_plane_atomic_destroy_state(plane, plane->state); - drm_atomic_helper_plane_reset(plane); - if (!plane->state) + omap_state = kzalloc(sizeof(*omap_state), GFP_KERNEL); + if (!omap_state) return; + omap_state->base.plane = plane; + plane->state = &omap_state->base; + plane->state->plane = plane; + plane->state->rotation = DRM_MODE_ROTATE_0; /* * Set the zpos default depending on whether we are a primary or overlay * plane. */ plane->state->zpos = plane->type == DRM_PLANE_TYPE_PRIMARY - ? 0 : omap_plane->id; + ? 0 : omap_plane->default_id; +} + +static void omap_plane_atomic_print_state(struct drm_printer *p, + const struct drm_plane_state *state) +{ + struct omap_plane_state *omap_state = to_omap_plane_state(state); + + drm_printf(p, "\toverlay=%p\n", omap_state->overlay); + if (omap_state->overlay) { + drm_printf(p, "\t\tidx=%d\n", omap_state->overlay->idx); + drm_printf(p, "\t\toverlay_id=%d\n", omap_state->overlay->overlay_id); + drm_printf(p, "\t\tcaps=0x%x\n", omap_state->overlay->caps); + drm_printf(p, "\t\tpossible_crtcs=0x%x\n", omap_state->overlay->possible_crtcs); + } + + drm_printf(p, "\tr_overlay=%p\n", omap_state->r_overlay); + if (omap_state->r_overlay) { + drm_printf(p, "\t\tidx=%d\n", omap_state->r_overlay->idx); + drm_printf(p, "\t\toverlay_id=%d\n", omap_state->r_overlay->overlay_id); + drm_printf(p, "\t\tcaps=0x%x\n", omap_state->r_overlay->caps); + drm_printf(p, "\t\tpossible_crtcs=0x%x\n", omap_state->r_overlay->possible_crtcs); + } } static int omap_plane_atomic_set_property(struct drm_plane *plane, @@ -233,10 +459,11 @@ static const struct drm_plane_funcs omap_plane_funcs = { .disable_plane = drm_atomic_helper_disable_plane, .reset = omap_plane_reset, .destroy = omap_plane_destroy, - .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = omap_plane_atomic_duplicate_state, + .atomic_destroy_state = omap_plane_atomic_destroy_state, .atomic_set_property = omap_plane_atomic_set_property, .atomic_get_property = omap_plane_atomic_get_property, + .atomic_print_state = omap_plane_atomic_print_state, }; static const char *plane_id_to_name[] = { @@ -246,14 +473,6 @@ static const char *plane_id_to_name[] = { [OMAP_DSS_VIDEO3] = "vid3", }; -static const enum omap_plane_id plane_idx_to_id[] = { - OMAP_DSS_GFX, - OMAP_DSS_VIDEO1, - OMAP_DSS_VIDEO2, - OMAP_DSS_VIDEO3, -}; - -/* initialize plane */ struct drm_plane *omap_plane_init(struct drm_device *dev, int idx, enum drm_plane_type type, u32 possible_crtcs) @@ -262,27 +481,28 @@ struct drm_plane *omap_plane_init(struct drm_device *dev, unsigned int num_planes = priv->dispc_ops->get_num_ovls(priv->dispc); struct drm_plane *plane; struct omap_plane *omap_plane; - enum omap_plane_id id; int ret; u32 nformats; const u32 *formats; - if (WARN_ON(idx >= ARRAY_SIZE(plane_idx_to_id))) + if (WARN_ON(idx >= num_planes)) return ERR_PTR(-EINVAL); - id = plane_idx_to_id[idx]; - - DBG("%s: type=%d", plane_id_to_name[id], type); - omap_plane = kzalloc(sizeof(*omap_plane), GFP_KERNEL); if (!omap_plane) return ERR_PTR(-ENOMEM); - formats = priv->dispc_ops->ovl_get_color_modes(priv->dispc, id); + omap_plane->default_id = idx; + omap_plane->name = plane_id_to_name[idx]; + + DBG("%s: type=%d", omap_plane->name, type); + DBG(" omap_plane->default_id: %d", omap_plane->default_id); + DBG(" crtc_mask: 0x%04x", possible_crtcs); + + formats = priv->dispc_ops->ovl_get_color_modes(priv->dispc, + omap_plane->default_id); for (nformats = 0; formats[nformats]; ++nformats) ; - omap_plane->id = id; - omap_plane->name = plane_id_to_name[id]; plane = &omap_plane->base; @@ -301,7 +521,7 @@ struct drm_plane *omap_plane_init(struct drm_device *dev, error: dev_err(dev->dev, "%s(): could not create plane: %s\n", - __func__, plane_id_to_name[id]); + __func__, omap_plane->name); kfree(omap_plane); return NULL; diff --git a/drivers/gpu/drm/omapdrm/omap_plane.h b/drivers/gpu/drm/omapdrm/omap_plane.h index dc5e82ad061d..9000f45a9b65 100644 --- a/drivers/gpu/drm/omapdrm/omap_plane.h +++ b/drivers/gpu/drm/omapdrm/omap_plane.h @@ -28,10 +28,30 @@ struct drm_device; struct drm_mode_object; struct drm_plane; +/* + * Atomic plane state. Subclasses the base drm_plane_state in order to + * track assigned overlay and hw specific state. + */ +struct omap_plane_state { + struct drm_plane_state base; + + struct omap_hw_overlay *overlay; + struct omap_hw_overlay *r_overlay; /* right overlay */ +}; +#define to_omap_plane_state(x) \ + container_of(x, struct omap_plane_state, base) + struct drm_plane *omap_plane_init(struct drm_device *dev, int idx, enum drm_plane_type type, u32 possible_crtcs); void omap_plane_install_properties(struct drm_plane *plane, struct drm_mode_object *obj); +static inline bool is_omap_plane_dual_overlay(struct drm_plane_state *state) +{ + struct omap_plane_state *omap_state = to_omap_plane_state(state); + + return !!omap_state->r_overlay; +} + #endif /* __OMAPDRM_PLANE_H__ */ -- 2.9.0 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel