drm/meson: Add primary plane scaling
[sfrench/cifs-2.6.git] / drivers / gpu / drm / meson / meson_plane.c
index 8712498f9e934ed40f013d324275fcf34f9bf8f3..12a47b4f65a533eee9210b571738a41248ed357e 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/bitfield.h>
 #include <linux/platform_device.h>
 #include <drm/drmP.h>
 #include <drm/drm_atomic.h>
 #include "meson_canvas.h"
 #include "meson_registers.h"
 
+/* OSD_SCI_WH_M1 */
+#define SCI_WH_M1_W(w)                 FIELD_PREP(GENMASK(28, 16), w)
+#define SCI_WH_M1_H(h)                 FIELD_PREP(GENMASK(12, 0), h)
+
+/* OSD_SCO_H_START_END */
+/* OSD_SCO_V_START_END */
+#define SCO_HV_START(start)            FIELD_PREP(GENMASK(27, 16), start)
+#define SCO_HV_END(end)                        FIELD_PREP(GENMASK(11, 0), end)
+
+/* OSD_SC_CTRL0 */
+#define SC_CTRL0_PATH_EN               BIT(3)
+#define SC_CTRL0_SEL_OSD1              BIT(2)
+
+/* OSD_VSC_CTRL0 */
+#define VSC_BANK_LEN(value)            FIELD_PREP(GENMASK(2, 0), value)
+#define VSC_TOP_INI_RCV_NUM(value)     FIELD_PREP(GENMASK(6, 3), value)
+#define VSC_TOP_RPT_L0_NUM(value)      FIELD_PREP(GENMASK(9, 8), value)
+#define VSC_BOT_INI_RCV_NUM(value)     FIELD_PREP(GENMASK(14, 11), value)
+#define VSC_BOT_RPT_L0_NUM(value)      FIELD_PREP(GENMASK(17, 16), value)
+#define VSC_PROG_INTERLACE             BIT(23)
+#define VSC_VERTICAL_SCALER_EN         BIT(24)
+
+/* OSD_VSC_INI_PHASE */
+#define VSC_INI_PHASE_BOT(bottom)      FIELD_PREP(GENMASK(31, 16), bottom)
+#define VSC_INI_PHASE_TOP(top)         FIELD_PREP(GENMASK(15, 0), top)
+
+/* OSD_HSC_CTRL0 */
+#define HSC_BANK_LENGTH(value)         FIELD_PREP(GENMASK(2, 0), value)
+#define HSC_INI_RCV_NUM0(value)                FIELD_PREP(GENMASK(6, 3), value)
+#define HSC_RPT_P0_NUM0(value)         FIELD_PREP(GENMASK(9, 8), value)
+#define HSC_HORIZ_SCALER_EN            BIT(22)
+
+/* VPP_OSD_VSC_PHASE_STEP */
+/* VPP_OSD_HSC_PHASE_STEP */
+#define SC_PHASE_STEP(value)           FIELD_PREP(GENMASK(27, 0), value)
+
 struct meson_plane {
        struct drm_plane base;
        struct meson_drm *priv;
 };
 #define to_meson_plane(x) container_of(x, struct meson_plane, base)
 
+#define FRAC_16_16(mult, div)    (((mult) << 16) / (div))
+
 static int meson_plane_atomic_check(struct drm_plane *plane,
                                    struct drm_plane_state *state)
 {
@@ -57,10 +96,15 @@ static int meson_plane_atomic_check(struct drm_plane *plane,
        if (IS_ERR(crtc_state))
                return PTR_ERR(crtc_state);
 
+       /*
+        * Only allow :
+        * - Upscaling up to 5x, vertical and horizontal
+        * - Final coordinates must match crtc size
+        */
        return drm_atomic_helper_check_plane_state(state, crtc_state,
+                                                  FRAC_16_16(1, 5),
                                                   DRM_PLANE_HELPER_NO_SCALING,
-                                                  DRM_PLANE_HELPER_NO_SCALING,
-                                                  true, true);
+                                                  false, true);
 }
 
 /* Takes a fixed 16.16 number and converts it to integer. */
@@ -74,22 +118,19 @@ static void meson_plane_atomic_update(struct drm_plane *plane,
 {
        struct meson_plane *meson_plane = to_meson_plane(plane);
        struct drm_plane_state *state = plane->state;
-       struct drm_framebuffer *fb = state->fb;
+       struct drm_rect dest = drm_plane_state_dest(state);
        struct meson_drm *priv = meson_plane->priv;
+       struct drm_framebuffer *fb = state->fb;
        struct drm_gem_cma_object *gem;
-       struct drm_rect src = {
-               .x1 = (state->src_x),
-               .y1 = (state->src_y),
-               .x2 = (state->src_x + state->src_w),
-               .y2 = (state->src_y + state->src_h),
-       };
-       struct drm_rect dest = {
-               .x1 = state->crtc_x,
-               .y1 = state->crtc_y,
-               .x2 = state->crtc_x + state->crtc_w,
-               .y2 = state->crtc_y + state->crtc_h,
-       };
        unsigned long flags;
+       int vsc_ini_rcv_num, vsc_ini_rpt_p0_num;
+       int vsc_bot_rcv_num, vsc_bot_rpt_p0_num;
+       int hsc_ini_rcv_num, hsc_ini_rpt_p0_num;
+       int hf_phase_step, vf_phase_step;
+       int src_w, src_h, dst_w, dst_h;
+       int bot_ini_phase;
+       int hf_bank_len;
+       int vf_bank_len;
        u8 canvas_id_osd1;
 
        /*
@@ -143,6 +184,27 @@ static void meson_plane_atomic_update(struct drm_plane *plane,
                break;
        };
 
+       /* Default scaler parameters */
+       vsc_bot_rcv_num = 0;
+       vsc_bot_rpt_p0_num = 0;
+       hf_bank_len = 4;
+       vf_bank_len = 4;
+
+       if (state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE) {
+               vsc_bot_rcv_num = 6;
+               vsc_bot_rpt_p0_num = 2;
+       }
+
+       hsc_ini_rcv_num = hf_bank_len;
+       vsc_ini_rcv_num = vf_bank_len;
+       hsc_ini_rpt_p0_num = (hf_bank_len / 2) - 1;
+       vsc_ini_rpt_p0_num = (vf_bank_len / 2) - 1;
+
+       src_w = fixed16_to_int(state->src_w);
+       src_h = fixed16_to_int(state->src_h);
+       dst_w = state->crtc_w;
+       dst_h = state->crtc_h;
+
        /*
         * When the output is interlaced, the OSD must switch between
         * each field using the INTERLACE_SEL_ODD (0) of VIU_OSD1_BLK0_CFG_W0
@@ -151,41 +213,73 @@ static void meson_plane_atomic_update(struct drm_plane *plane,
         * is configured for 2:1 scaling with interlace options enabled.
         */
        if (state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE) {
-               priv->viu.osd1_interlace = true;
-
                dest.y1 /= 2;
                dest.y2 /= 2;
+               dst_h /= 2;
+       }
 
-               priv->viu.osd_sc_ctrl0 = BIT(3) | /* Enable scaler */
-                                        BIT(2); /* Select OSD1 */
+       hf_phase_step = ((src_w << 18) / dst_w) << 6;
+       vf_phase_step = (src_h << 20) / dst_h;
 
-               /* 2:1 scaling */
-               priv->viu.osd_sc_i_wh_m1 = ((drm_rect_width(&dest) - 1) << 16) |
-                                          (drm_rect_height(&dest) - 1);
-               priv->viu.osd_sc_o_h_start_end = (dest.x1 << 16) | dest.x2;
-               priv->viu.osd_sc_o_v_start_end = (dest.y1 << 16) | dest.y2;
+       if (state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE)
+               bot_ini_phase = ((vf_phase_step / 2) >> 4);
+       else
+               bot_ini_phase = 0;
+
+       vf_phase_step = (vf_phase_step << 4);
+
+       /* In interlaced mode, scaler is always active */
+       if (src_h != dst_h || src_w != dst_w) {
+               priv->viu.osd_sc_i_wh_m1 = SCI_WH_M1_W(src_w - 1) |
+                                          SCI_WH_M1_H(src_h - 1);
+               priv->viu.osd_sc_o_h_start_end = SCO_HV_START(dest.x1) |
+                                                SCO_HV_END(dest.x2 - 1);
+               priv->viu.osd_sc_o_v_start_end = SCO_HV_START(dest.y1) |
+                                                SCO_HV_END(dest.y2 - 1);
+               /* Enable OSD Scaler */
+               priv->viu.osd_sc_ctrl0 = SC_CTRL0_PATH_EN | SC_CTRL0_SEL_OSD1;
+       } else {
+               priv->viu.osd_sc_i_wh_m1 = 0;
+               priv->viu.osd_sc_o_h_start_end = 0;
+               priv->viu.osd_sc_o_v_start_end = 0;
+               priv->viu.osd_sc_ctrl0 = 0;
+       }
 
-               /* 2:1 vertical scaling values */
-               priv->viu.osd_sc_v_ini_phase = BIT(16);
-               priv->viu.osd_sc_v_phase_step = BIT(25);
+       /* In interlaced mode, vertical scaler is always active */
+       if (src_h != dst_h) {
                priv->viu.osd_sc_v_ctrl0 =
-                       (4 << 0) | /* osd_vsc_bank_length */
-                       (4 << 3) | /* osd_vsc_top_ini_rcv_num0 */
-                       (1 << 8) | /* osd_vsc_top_rpt_p0_num0 */
-                       (6 << 11) | /* osd_vsc_bot_ini_rcv_num0 */
-                       (2 << 16) | /* osd_vsc_bot_rpt_p0_num0 */
-                       BIT(23) | /* osd_prog_interlace */
-                       BIT(24); /* Enable vertical scaler */
-
-               /* No horizontal scaling */
+                                       VSC_BANK_LEN(vf_bank_len) |
+                                       VSC_TOP_INI_RCV_NUM(vsc_ini_rcv_num) |
+                                       VSC_TOP_RPT_L0_NUM(vsc_ini_rpt_p0_num) |
+                                       VSC_VERTICAL_SCALER_EN;
+
+               if (state->crtc->mode.flags & DRM_MODE_FLAG_INTERLACE)
+                       priv->viu.osd_sc_v_ctrl0 |=
+                                       VSC_BOT_INI_RCV_NUM(vsc_bot_rcv_num) |
+                                       VSC_BOT_RPT_L0_NUM(vsc_bot_rpt_p0_num) |
+                                       VSC_PROG_INTERLACE;
+
+               priv->viu.osd_sc_v_phase_step = SC_PHASE_STEP(vf_phase_step);
+               priv->viu.osd_sc_v_ini_phase = VSC_INI_PHASE_BOT(bot_ini_phase);
+       } else {
+               priv->viu.osd_sc_v_ctrl0 = 0;
+               priv->viu.osd_sc_v_phase_step = 0;
+               priv->viu.osd_sc_v_ini_phase = 0;
+       }
+
+       /* Horizontal scaler is only used if width does not match */
+       if (src_w != dst_w) {
+               priv->viu.osd_sc_h_ctrl0 =
+                                       HSC_BANK_LENGTH(hf_bank_len) |
+                                       HSC_INI_RCV_NUM0(hsc_ini_rcv_num) |
+                                       HSC_RPT_P0_NUM0(hsc_ini_rpt_p0_num) |
+                                       HSC_HORIZ_SCALER_EN;
+               priv->viu.osd_sc_h_phase_step = SC_PHASE_STEP(hf_phase_step);
                priv->viu.osd_sc_h_ini_phase = 0;
-               priv->viu.osd_sc_h_phase_step = 0;
-               priv->viu.osd_sc_h_ctrl0 = 0;
        } else {
-               priv->viu.osd1_interlace = false;
-               priv->viu.osd_sc_ctrl0 = 0;
                priv->viu.osd_sc_h_ctrl0 = 0;
-               priv->viu.osd_sc_v_ctrl0 = 0;
+               priv->viu.osd_sc_h_phase_step = 0;
+               priv->viu.osd_sc_h_ini_phase = 0;
        }
 
        /*
@@ -193,10 +287,12 @@ static void meson_plane_atomic_update(struct drm_plane *plane,
         * where x2 is exclusive.
         * e.g. +30x1920 would be (1919 << 16) | 30
         */
-       priv->viu.osd1_blk0_cfg[1] = ((fixed16_to_int(src.x2) - 1) << 16) |
-                                       fixed16_to_int(src.x1);
-       priv->viu.osd1_blk0_cfg[2] = ((fixed16_to_int(src.y2) - 1) << 16) |
-                                       fixed16_to_int(src.y1);
+       priv->viu.osd1_blk0_cfg[1] =
+                               ((fixed16_to_int(state->src.x2) - 1) << 16) |
+                               fixed16_to_int(state->src.x1);
+       priv->viu.osd1_blk0_cfg[2] =
+                               ((fixed16_to_int(state->src.y2) - 1) << 16) |
+                               fixed16_to_int(state->src.y1);
        priv->viu.osd1_blk0_cfg[3] = ((dest.x2 - 1) << 16) | dest.x1;
        priv->viu.osd1_blk0_cfg[4] = ((dest.y2 - 1) << 16) | dest.y1;