@@ -49,6 +49,11 @@ struct vc4_crtc_state {
struct drm_mm_node mm;
bool feed_txp;
bool txp_armed;
+
+ struct {
+ unsigned int vborder;
+ unsigned int hborder;
+ } underscan;
};
static inline struct vc4_crtc_state *
@@ -624,6 +629,39 @@ static enum drm_mode_status vc4_crtc_mode_valid(struct drm_crtc *crtc,
return MODE_OK;
}
+void vc4_crtc_get_underscan_borders(struct drm_crtc_state *state,
+ unsigned int *vborder,
+ unsigned int *hborder)
+{
+ struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
+ struct drm_connector_state *conn_state;
+ struct drm_connector *conn;
+ int i;
+
+ *vborder = vc4_state->underscan.vborder;
+ *hborder = vc4_state->underscan.hborder;
+
+ /* We have to interate over all new connector states because
+ * vc4_crtc_get_underscan_borders() might be called before
+ * vc4_crtc_atomic_check() which means underscan 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;
+
+ if (conn_state->underscan.mode == DRM_UNDERSCAN_ON) {
+ *vborder = conn_state->underscan.vborder;
+ *hborder = conn_state->underscan.hborder;
+ } else {
+ *vborder = 0;
+ *hborder = 0;
+ }
+
+ break;
+ }
+}
+
static int vc4_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
@@ -657,6 +695,7 @@ static int vc4_crtc_atomic_check(struct drm_crtc *crtc,
return ret;
for_each_new_connector_in_state(state->state, conn, conn_state, i) {
+ unsigned int vborder = 0, hborder = 0;
if (conn_state->crtc != crtc)
continue;
@@ -671,6 +710,13 @@ static int vc4_crtc_atomic_check(struct drm_crtc *crtc,
vc4_state->feed_txp = false;
}
+ if (conn_state->underscan.mode == DRM_UNDERSCAN_ON) {
+ vborder = conn_state->underscan.vborder;
+ hborder = conn_state->underscan.hborder;
+ }
+
+ vc4_state->underscan.vborder = vborder;
+ vc4_state->underscan.hborder = hborder;
break;
}
@@ -972,6 +1018,7 @@ static struct drm_crtc_state *vc4_crtc_duplicate_state(struct drm_crtc *crtc)
old_vc4_state = to_vc4_crtc_state(crtc->state);
vc4_state->feed_txp = old_vc4_state->feed_txp;
+ vc4_state->underscan = old_vc4_state->underscan;
__drm_atomic_helper_crtc_duplicate_state(crtc, &vc4_state->base);
return &vc4_state->base;
@@ -707,6 +707,9 @@ bool vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id,
const struct drm_display_mode *mode);
void vc4_crtc_handle_vblank(struct vc4_crtc *crtc);
void vc4_crtc_txp_armed(struct drm_crtc_state *state);
+void vc4_crtc_get_underscan_borders(struct drm_crtc_state *state,
+ unsigned int *vborder,
+ unsigned int *hborder);
/* vc4_debugfs.c */
int vc4_debugfs_init(struct drm_minor *minor);
@@ -258,6 +258,52 @@ static u32 vc4_get_scl_field(struct drm_plane_state *state, int plane)
}
}
+static int vc4_plane_underscan_adj(struct drm_plane_state *pstate)
+{
+ struct vc4_plane_state *vc4_pstate = to_vc4_plane_state(pstate);
+ unsigned int vborder, hborder, adjhdisplay, adjvdisplay;
+ struct drm_crtc_state *crtc_state;
+
+ crtc_state = drm_atomic_get_new_crtc_state(pstate->state,
+ pstate->crtc);
+
+ vc4_crtc_get_underscan_borders(crtc_state, &vborder, &hborder);
+ if (!vborder && !hborder)
+ return 0;
+
+ if (hborder * 2 >= crtc_state->mode.hdisplay ||
+ vborder * 2 >= crtc_state->mode.vdisplay)
+ return -EINVAL;
+
+ adjhdisplay = crtc_state->mode.hdisplay - (2 * hborder);
+ vc4_pstate->crtc_x = DIV_ROUND_CLOSEST(vc4_pstate->crtc_x *
+ adjhdisplay,
+ crtc_state->mode.hdisplay);
+ vc4_pstate->crtc_x += hborder;
+ if (vc4_pstate->crtc_x > crtc_state->mode.hdisplay - hborder)
+ vc4_pstate->crtc_x = crtc_state->mode.hdisplay - hborder;
+
+ adjvdisplay = crtc_state->mode.vdisplay - (2 * vborder);
+ vc4_pstate->crtc_y = DIV_ROUND_CLOSEST(vc4_pstate->crtc_y *
+ adjvdisplay,
+ crtc_state->mode.vdisplay);
+ vc4_pstate->crtc_y += vborder;
+ if (vc4_pstate->crtc_y > crtc_state->mode.vdisplay - vborder)
+ vc4_pstate->crtc_y = crtc_state->mode.vdisplay - vborder;
+
+ vc4_pstate->crtc_w = DIV_ROUND_CLOSEST(vc4_pstate->crtc_w *
+ adjhdisplay,
+ crtc_state->mode.hdisplay);
+ vc4_pstate->crtc_h = DIV_ROUND_CLOSEST(vc4_pstate->crtc_h *
+ adjvdisplay,
+ crtc_state->mode.vdisplay);
+
+ if (!vc4_pstate->crtc_w || !vc4_pstate->crtc_h)
+ return -EINVAL;
+
+ return 0;
+}
+
static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state)
{
struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
@@ -306,6 +352,10 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state)
vc4_state->crtc_w = state->dst.x2 - state->dst.x1;
vc4_state->crtc_h = state->dst.y2 - state->dst.y1;
+ ret = vc4_plane_underscan_adj(state);
+ if (ret)
+ return ret;
+
vc4_state->x_scaling[0] = vc4_get_scaling_mode(vc4_state->src_w[0],
vc4_state->crtc_w);
vc4_state->y_scaling[0] = vc4_get_scaling_mode(vc4_state->src_h[0],
Applying an underscan setup is just a matter of scaling all planes appropriately and adjusting the CRTC X/Y offset to account for the horizontal and vertical border. Create an vc4_plane_underscan_adj() function doing that and call it from vc4_plane_setup_clipping_and_scaling() so that we are ready to attach underscan properties to the HDMI connector. Signed-off-by: Boris Brezillon <boris.brezillon@bootlin.com> --- Changes in v3: - Rebase on top of the "cursor rescaling" changes Changes in v2: - Take changes on hborder/vborder meaning into account --- drivers/gpu/drm/vc4/vc4_crtc.c | 47 +++++++++++++++++++++++++++++++ drivers/gpu/drm/vc4/vc4_drv.h | 3 ++ drivers/gpu/drm/vc4/vc4_plane.c | 50 +++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+)