drm/vc4: Add support for margins to fkms
authorDave Stevenson <dave.stevenson@raspberrypi.org>
Fri, 19 Jul 2019 14:35:13 +0000 (15:35 +0100)
committerpopcornmix <popcornmix@gmail.com>
Tue, 23 Jul 2019 18:05:50 +0000 (19:05 +0100)
Allows for overscan to be configured under FKMS.
NB This is rescaling the planes, not reducing the size of the
display mode.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.org>
drivers/gpu/drm/vc4/vc4_firmware_kms.c

index 7a6c45e..2c564b0 100644 (file)
@@ -256,6 +256,23 @@ static inline struct vc4_crtc *to_vc4_crtc(struct drm_crtc *crtc)
        return container_of(crtc, struct vc4_crtc, base);
 }
 
+struct vc4_crtc_state {
+       struct drm_crtc_state base;
+
+       struct {
+               unsigned int left;
+               unsigned int right;
+               unsigned int top;
+               unsigned int bottom;
+       } margins;
+};
+
+static inline struct vc4_crtc_state *
+to_vc4_crtc_state(struct drm_crtc_state *crtc_state)
+{
+       return (struct vc4_crtc_state *)crtc_state;
+}
+
 struct vc4_fkms_encoder {
        struct drm_encoder base;
        bool hdmi_monitor;
@@ -365,17 +382,127 @@ static int vc4_plane_set_blank(struct drm_plane *plane, bool blank)
        return ret;
 }
 
+static void vc4_fkms_crtc_get_margins(struct drm_crtc_state *state,
+                                     unsigned int *left, unsigned int *right,
+                                     unsigned int *top, unsigned int *bottom)
+{
+       struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
+       struct drm_connector_state *conn_state;
+       struct drm_connector *conn;
+       int i;
+
+       *left = vc4_state->margins.left;
+       *right = vc4_state->margins.right;
+       *top = vc4_state->margins.top;
+       *bottom = vc4_state->margins.bottom;
+
+       /* We have to interate over all new connector states because
+        * vc4_fkms_crtc_get_margins() might be called before
+        * vc4_fkms_crtc_atomic_check() which means margins info in
+        * vc4_crtc_state might be outdated.
+        */
+       for_each_new_connector_in_state(state->state, conn, conn_state, i) {
+               if (conn_state->crtc != state->crtc)
+                       continue;
+
+               *left = conn_state->tv.margins.left;
+               *right = conn_state->tv.margins.right;
+               *top = conn_state->tv.margins.top;
+               *bottom = conn_state->tv.margins.bottom;
+               break;
+       }
+}
+
+static int vc4_fkms_margins_adj(struct drm_plane_state *pstate,
+                               struct set_plane *plane)
+{
+       unsigned int left, right, top, bottom;
+       int adjhdisplay, adjvdisplay;
+       struct drm_crtc_state *crtc_state;
+
+       crtc_state = drm_atomic_get_new_crtc_state(pstate->state,
+                                                  pstate->crtc);
+
+       vc4_fkms_crtc_get_margins(crtc_state, &left, &right, &top, &bottom);
+
+       if (!left && !right && !top && !bottom)
+               return 0;
+
+       if (left + right >= crtc_state->mode.hdisplay ||
+           top + bottom >= crtc_state->mode.vdisplay)
+               return -EINVAL;
+
+       adjhdisplay = crtc_state->mode.hdisplay - (left + right);
+       plane->dst_x = DIV_ROUND_CLOSEST(plane->dst_x * adjhdisplay,
+                                        (int)crtc_state->mode.hdisplay);
+       plane->dst_x += left;
+       if (plane->dst_x > (int)(crtc_state->mode.hdisplay - left))
+               plane->dst_x = crtc_state->mode.hdisplay - left;
+
+       adjvdisplay = crtc_state->mode.vdisplay - (top + bottom);
+       plane->dst_y = DIV_ROUND_CLOSEST(plane->dst_y * adjvdisplay,
+                                        (int)crtc_state->mode.vdisplay);
+       plane->dst_y += top;
+       if (plane->dst_y > (int)(crtc_state->mode.vdisplay - top))
+               plane->dst_y = crtc_state->mode.vdisplay - top;
+
+       plane->dst_w = DIV_ROUND_CLOSEST(plane->dst_w * adjhdisplay,
+                                        crtc_state->mode.hdisplay);
+       plane->dst_h = DIV_ROUND_CLOSEST(plane->dst_h * adjvdisplay,
+                                        crtc_state->mode.vdisplay);
+
+       if (!plane->dst_w || !plane->dst_h)
+               return -EINVAL;
+
+       return 0;
+}
+
 static void vc4_plane_atomic_update(struct drm_plane *plane,
                                    struct drm_plane_state *old_state)
 {
        struct drm_plane_state *state = plane->state;
+
+       /*
+        * Do NOT set now, as we haven't checked if the crtc is active or not.
+        * Set from vc4_plane_set_blank instead.
+        *
+        * If the CRTC is on (or going to be on) and we're enabled,
+        * then unblank.  Otherwise, stay blank until CRTC enable.
+        */
+       if (state->crtc->state->active)
+               vc4_plane_set_blank(plane, false);
+}
+
+static void vc4_plane_atomic_disable(struct drm_plane *plane,
+                                    struct drm_plane_state *old_state)
+{
+       struct drm_plane_state *state = plane->state;
+       struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
+
+       DRM_DEBUG_ATOMIC("[PLANE:%d:%s] plane disable %dx%d@%d +%d,%d\n",
+                        plane->base.id, plane->name,
+                        state->crtc_w,
+                        state->crtc_h,
+                        vc4_plane->mb.plane.vc_image_type,
+                        state->crtc_x,
+                        state->crtc_y);
+       vc4_plane_set_blank(plane, true);
+}
+
+static bool plane_enabled(struct drm_plane_state *state)
+{
+       return state->fb && state->crtc;
+}
+
+static int vc4_plane_to_mb(struct drm_plane *plane,
+                          struct mailbox_set_plane *mb,
+                          struct drm_plane_state *state)
+{
        struct drm_framebuffer *fb = state->fb;
        struct drm_gem_cma_object *bo = drm_fb_cma_get_gem_obj(fb, 0);
        const struct drm_format_info *drm_fmt = fb->format;
        const struct vc_image_format *vc_fmt =
                                        vc4_get_vc_image_fmt(drm_fmt->format);
-       struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
-       struct mailbox_set_plane *mb = &vc4_plane->mb;
        int num_planes = fb->format->num_planes;
        struct drm_display_mode *mode = &state->crtc->mode;
        unsigned int rotation = SUPPORTED_ROTATIONS;
@@ -417,25 +544,7 @@ static void vc4_plane_atomic_update(struct drm_plane *plane,
                break;
        }
 
-       /* FIXME: If the dest rect goes off screen then clip the src rect so we
-        * don't have off-screen pixels.
-        */
-       if (plane->type == DRM_PLANE_TYPE_CURSOR) {
-               /* There is no scaling on the cursor plane, therefore the calcs
-                * to alter the source crop as the cursor goes off the screen
-                * are simple.
-                */
-               if (mb->plane.dst_x + mb->plane.dst_w > mode->hdisplay) {
-                       mb->plane.dst_w = mode->hdisplay - mb->plane.dst_x;
-                       mb->plane.src_w = (mode->hdisplay - mb->plane.dst_x)
-                                                                       << 16;
-               }
-               if (mb->plane.dst_y + mb->plane.dst_h > mode->vdisplay) {
-                       mb->plane.dst_h = mode->vdisplay - mb->plane.dst_y;
-                       mb->plane.src_h = (mode->vdisplay - mb->plane.dst_y)
-                                                                       << 16;
-               }
-       }
+       vc4_fkms_margins_adj(state, &mb->plane);
 
        if (num_planes > 1) {
                /* Assume this must be YUV */
@@ -525,38 +634,19 @@ static void vc4_plane_atomic_update(struct drm_plane *plane,
                         state->alpha,
                         state->normalized_zpos);
 
-       /*
-        * Do NOT set now, as we haven't checked if the crtc is active or not.
-        * Set from vc4_plane_set_blank instead.
-        *
-        * If the CRTC is on (or going to be on) and we're enabled,
-        * then unblank.  Otherwise, stay blank until CRTC enable.
-        */
-       if (state->crtc->state->active)
-               vc4_plane_set_blank(plane, false);
+       return 0;
 }
 
-static void vc4_plane_atomic_disable(struct drm_plane *plane,
-                                    struct drm_plane_state *old_state)
+static int vc4_plane_atomic_check(struct drm_plane *plane,
+                                 struct drm_plane_state *state)
 {
-       //struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
-       struct drm_plane_state *state = plane->state;
        struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
 
-       DRM_DEBUG_ATOMIC("[PLANE:%d:%s] plane disable %dx%d@%d +%d,%d\n",
-                        plane->base.id, plane->name,
-                        state->crtc_w,
-                        state->crtc_h,
-                        vc4_plane->mb.plane.vc_image_type,
-                        state->crtc_x,
-                        state->crtc_y);
-       vc4_plane_set_blank(plane, true);
-}
+       if (!plane_enabled(state))
+               return 0;
+
+       return vc4_plane_to_mb(plane, &vc4_plane->mb, state);
 
-static int vc4_plane_atomic_check(struct drm_plane *plane,
-                                 struct drm_plane_state *state)
-{
-       return 0;
 }
 
 static void vc4_plane_destroy(struct drm_plane *plane)
@@ -878,8 +968,23 @@ vc4_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
 static int vc4_crtc_atomic_check(struct drm_crtc *crtc,
                                 struct drm_crtc_state *state)
 {
-       DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_check.\n",
-                     crtc->base.id);
+       struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
+       struct drm_connector *conn;
+       struct drm_connector_state *conn_state;
+       int i;
+
+       DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_check.\n", crtc->base.id);
+
+       for_each_new_connector_in_state(state->state, conn, conn_state, i) {
+               if (conn_state->crtc != crtc)
+                       continue;
+
+               vc4_state->margins.left = conn_state->tv.margins.left;
+               vc4_state->margins.right = conn_state->tv.margins.right;
+               vc4_state->margins.top = conn_state->tv.margins.top;
+               vc4_state->margins.bottom = conn_state->tv.margins.bottom;
+               break;
+       }
        return 0;
 }
 
@@ -980,6 +1085,33 @@ static int vc4_page_flip(struct drm_crtc *crtc,
        return drm_atomic_helper_page_flip(crtc, fb, event, flags, ctx);
 }
 
+static struct drm_crtc_state *
+vc4_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+       struct vc4_crtc_state *vc4_state, *old_vc4_state;
+
+       vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
+       if (!vc4_state)
+               return NULL;
+
+       old_vc4_state = to_vc4_crtc_state(crtc->state);
+       vc4_state->margins = old_vc4_state->margins;
+
+       __drm_atomic_helper_crtc_duplicate_state(crtc, &vc4_state->base);
+       return &vc4_state->base;
+}
+
+static void
+vc4_crtc_reset(struct drm_crtc *crtc)
+{
+       if (crtc->state)
+               __drm_atomic_helper_crtc_destroy_state(crtc->state);
+
+       crtc->state = kzalloc(sizeof(*crtc->state), GFP_KERNEL);
+       if (crtc->state)
+               crtc->state->crtc = crtc;
+}
+
 static int vc4_fkms_enable_vblank(struct drm_crtc *crtc)
 {
        struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
@@ -1007,8 +1139,8 @@ static const struct drm_crtc_funcs vc4_crtc_funcs = {
        .set_property = NULL,
        .cursor_set = NULL, /* handled by drm_mode_cursor_universal */
        .cursor_move = NULL, /* handled by drm_mode_cursor_universal */
-       .reset = drm_atomic_helper_crtc_reset,
-       .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+       .reset = vc4_crtc_reset,
+       .atomic_duplicate_state = vc4_crtc_duplicate_state,
        .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
        .enable_vblank = vc4_fkms_enable_vblank,
        .disable_vblank = vc4_fkms_disable_vblank,
@@ -1267,6 +1399,13 @@ vc4_fkms_connector_init(struct drm_device *dev, struct drm_encoder *encoder,
                connector->interlace_allowed = 0;
        }
 
+       /* Create and attach TV margin props to this connector. */
+       ret = drm_mode_create_tv_margin_properties(dev);
+       if (ret)
+               return ERR_PTR(ret);
+
+       drm_connector_attach_tv_margin_properties(connector);
+
        connector->polled = (DRM_CONNECTOR_POLL_CONNECT |
                             DRM_CONNECTOR_POLL_DISCONNECT);