From 04b88e1a8b867b045bc90d997e8e0260b00dbb15 Mon Sep 17 00:00:00 2001 From: Nick Hollinghurst Date: Thu, 11 Jan 2024 12:13:03 +0000 Subject: [PATCH] drm: rp1: rp1-vec: Allow non-standard modes with various crops Tweak sync timings in the advertised modelines. Accept other, custom modes, provided they fit within the active area of one of the existing hardware-supported TV modes. Instead of always padding symmetrically, try to respect the user's [hv]sync_start values, allowing the image to be shifted around the screen (to fine-tune overscan correction). Signed-off-by: Nick Hollinghurst --- drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c | 66 ++++++++++++--------- drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c | 74 ++++++++++++++++++------ 2 files changed, 96 insertions(+), 44 deletions(-) --- a/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c +++ b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -208,26 +207,26 @@ static void rp1vec_connector_destroy(str static const struct drm_display_mode rp1vec_modes[4] = { { /* Full size 525/60i with Rec.601 pixel rate */ DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500, - 720, 720 + 14, 720 + 14 + 64, 858, 0, - 480, 480 + 7, 480 + 7 + 6, 525, 0, + 720, 720 + 16, 720 + 16 + 64, 858, 0, + 480, 480 + 6, 480 + 6 + 6, 525, 0, DRM_MODE_FLAG_INTERLACE) }, { /* Cropped and horizontally squashed to be TV-safe */ DRM_MODE("704x432i", DRM_MODE_TYPE_DRIVER, 15429, - 704, 704 + 72, 704 + 72 + 72, 980, 0, - 432, 432 + 31, 432 + 31 + 6, 525, 0, + 704, 704 + 76, 704 + 76 + 72, 980, 0, + 432, 432 + 30, 432 + 30 + 6, 525, 0, DRM_MODE_FLAG_INTERLACE) }, { /* Full size 625/50i with Rec.601 pixel rate */ DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500, 720, 720 + 20, 720 + 20 + 64, 864, 0, - 576, 576 + 4, 576 + 4 + 6, 625, 0, + 576, 576 + 5, 576 + 5 + 5, 625, 0, DRM_MODE_FLAG_INTERLACE) }, { /* Cropped and squashed, for square(ish) pixels */ DRM_MODE("704x512i", DRM_MODE_TYPE_DRIVER, 15429, 704, 704 + 80, 704 + 80 + 72, 987, 0, - 512, 512 + 36, 512 + 36 + 6, 625, 0, + 512, 512 + 37, 512 + 37 + 5, 625, 0, DRM_MODE_FLAG_INTERLACE) } }; @@ -298,27 +297,42 @@ static enum drm_mode_status rp1vec_mode_ const struct drm_display_mode *mode) { /* - * Check the mode roughly matches one of our standard modes - * (optionally half-height and progressive). Ignore H/V sync - * timings which for interlaced TV are approximate at best. + * Check the mode roughly matches something we can generate. + * The hardware driver is very prescriptive about pixel clocks, + * line and frame durations, but we'll tolerate rounding errors. + * Within each hardware mode, allow image size and position to vary + * (to fine-tune overscan correction or emulate retro devices). + * Don't check sync timings here: the HW driver will sanitize them. */ - int i, prog; - prog = !(mode->flags & DRM_MODE_FLAG_INTERLACE); - - for (i = 0; i < ARRAY_SIZE(rp1vec_modes); i++) { - const struct drm_display_mode *ref = rp1vec_modes + i; + int prog = !(mode->flags & DRM_MODE_FLAG_INTERLACE); + int vtotal_full = mode->vtotal << prog; + int vdisplay_full = mode->vdisplay << prog; + + /* Reject very small frames */ + if (vtotal_full < 256 || mode->hdisplay < 256) + return MODE_BAD; + + /* Check lines, frame period (ms) and vertical size limit */ + if (vtotal_full >= 524 && vtotal_full <= 526 && + mode->htotal * vtotal_full > 33 * mode->clock && + mode->htotal * vtotal_full < 34 * mode->clock && + vdisplay_full <= 480) + goto vgood; + if (vtotal_full >= 624 && vtotal_full <= 626 && + mode->htotal * vtotal_full > 39 * mode->clock && + mode->htotal * vtotal_full < 41 * mode->clock && + vdisplay_full <= 576) + goto vgood; + return MODE_BAD; - if (mode->hdisplay == ref->hdisplay && - mode->vdisplay == (ref->vdisplay >> prog) && - mode->clock + 2 >= ref->clock && - mode->clock <= ref->clock + 2 && - mode->htotal + 2 >= ref->htotal && - mode->htotal <= ref->htotal + 2 && - mode->vtotal + 2 >= (ref->vtotal >> prog) && - mode->vtotal <= (ref->vtotal >> prog) + 2) - return MODE_OK; - } +vgood: + /* Check pixel rate (kHz) and horizontal size limit */ + if (mode->clock == 13500 && mode->hdisplay <= 720) + return MODE_OK; + if (mode->clock >= 15428 && mode->clock <= 15429 && + mode->hdisplay <= 800) + return MODE_OK; return MODE_BAD; } @@ -440,7 +454,7 @@ static int rp1vec_platform_probe(struct ret = drmm_mode_config_init(drm); if (ret) goto err_free_drm; - drm->mode_config.max_width = 768; + drm->mode_config.max_width = 800; drm->mode_config.max_height = 576; drm->mode_config.fb_base = 0; drm->mode_config.preferred_depth = 32; --- a/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c +++ b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -82,13 +81,13 @@ static const struct rp1vec_ipixfmt my_fo /* * Hardware mode descriptions (@ 108 MHz clock rate). * These rely largely on "canned" register settings. - * TODO: Port the generating software from FP to integer, - * or better factorize the differences between modes. */ struct rp1vec_hwmode { - u16 total_cols; /* active columns, plus padding for filter context */ + u16 total_cols; /* max active columns incl. padding and windowing */ u16 rows_per_field; /* active lines per field (including partial ones) */ + u16 ref_hfp; /* nominal (hsync_start - hdisplay) when max width */ + u16 ref_vfp; /* nominal (vsync_start - vdisplay) when max height */ bool interlaced; /* set for interlaced */ bool first_field_odd; /* set for interlaced and 30fps */ u32 yuv_scaling; /* three 10-bit fields {Y, U, V} in 2.8 format */ @@ -103,6 +102,8 @@ static const struct rp1vec_hwmode rp1vec { .total_cols = 724, .rows_per_field = 240, + .ref_hfp = 12, + .ref_vfp = 2, .interlaced = false, .first_field_odd = false, .yuv_scaling = 0x1071d0cf, @@ -118,6 +119,8 @@ static const struct rp1vec_hwmode rp1vec }, { .total_cols = 815, .rows_per_field = 240, + .ref_hfp = 16, + .ref_vfp = 2, .interlaced = false, .first_field_odd = false, .yuv_scaling = 0x1c131962, @@ -135,6 +138,8 @@ static const struct rp1vec_hwmode rp1vec { .total_cols = 724, .rows_per_field = 243, + .ref_hfp = 12, + .ref_vfp = 3, .interlaced = true, .first_field_odd = true, .yuv_scaling = 0x1071d0cf, @@ -150,6 +155,8 @@ static const struct rp1vec_hwmode rp1vec }, { .total_cols = 815, .rows_per_field = 243, + .ref_hfp = 16, + .ref_vfp = 3, .interlaced = true, .first_field_odd = true, .yuv_scaling = 0x1c131962, @@ -170,6 +177,8 @@ static const struct rp1vec_hwmode rp1vec { .total_cols = 724, .rows_per_field = 288, + .ref_hfp = 16, + .ref_vfp = 2, .interlaced = false, .first_field_odd = false, .yuv_scaling = 0x11c1f8e0, @@ -185,6 +194,8 @@ static const struct rp1vec_hwmode rp1vec }, { .total_cols = 804, .rows_per_field = 288, + .ref_hfp = 24, + .ref_vfp = 2, .interlaced = false, .first_field_odd = false, .yuv_scaling = 0x1e635d7f, @@ -202,6 +213,8 @@ static const struct rp1vec_hwmode rp1vec { .total_cols = 724, .rows_per_field = 288, + .ref_hfp = 16, + .ref_vfp = 5, .interlaced = true, .first_field_odd = false, .yuv_scaling = 0x11c1f8e0, @@ -217,6 +230,8 @@ static const struct rp1vec_hwmode rp1vec }, { .total_cols = 804, .rows_per_field = 288, + .ref_hfp = 24, + .ref_vfp = 5, .interlaced = true, .first_field_odd = false, .yuv_scaling = 0x1e635d7f, @@ -237,6 +252,8 @@ static const struct rp1vec_hwmode rp1vec { .total_cols = 724, .rows_per_field = 240, + .ref_hfp = 12, + .ref_vfp = 2, .interlaced = false, .first_field_odd = false, .yuv_scaling = 0x11c1f8e0, @@ -252,6 +269,8 @@ static const struct rp1vec_hwmode rp1vec }, { .total_cols = 815, .rows_per_field = 240, + .ref_hfp = 16, + .ref_vfp = 2, .interlaced = false, .first_field_odd = false, .yuv_scaling = 0x1e635d7f, @@ -269,6 +288,8 @@ static const struct rp1vec_hwmode rp1vec { .total_cols = 724, .rows_per_field = 243, + .ref_hfp = 12, + .ref_vfp = 3, .interlaced = true, .first_field_odd = true, .yuv_scaling = 0x11c1f8e0, @@ -284,6 +305,8 @@ static const struct rp1vec_hwmode rp1vec }, { .total_cols = 815, .rows_per_field = 243, + .ref_hfp = 16, + .ref_vfp = 3, .interlaced = true, .first_field_odd = true, .yuv_scaling = 0x1e635d7f, @@ -308,11 +331,11 @@ void rp1vec_hw_setup(struct rp1_vec *vec { unsigned int i, mode_family, mode_ilaced, mode_narrow; const struct rp1vec_hwmode *hwm; - unsigned int w, h; + int w, h, hpad, vpad; /* Pick the appropriate "base" mode, which we may modify */ mode_ilaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); - if (mode->vtotal > 263 * (1 + mode_ilaced)) + if (mode->vtotal >= 272 * (1 + mode_ilaced)) mode_family = 1; else mode_family = (tvstd == RP1VEC_TVSTD_PAL_M || tvstd == RP1VEC_TVSTD_PAL60) ? 2 : 0; @@ -326,13 +349,28 @@ void rp1vec_hw_setup(struct rp1_vec *vec tvstd, rp1vec_tvstd_names[tvstd]); w = mode->hdisplay; - h = mode->vdisplay; - if (mode_ilaced) - h >>= 1; + h = mode->vdisplay >> mode_ilaced; if (w > hwm->total_cols) w = hwm->total_cols; if (h > hwm->rows_per_field) - w = hwm->rows_per_field; + h = hwm->rows_per_field; + + /* + * Add padding so a framebuffer with the given dimensions and + * [hv]sync_start can be displayed in the chosen hardware mode. + * + * |<----- mode->hsync_start ----->| + * |<------ w ------>| | + * | | >|--|< ref_hfp + * |<- hpad ->| + * |<------------ total_cols ----------->| + * ________FRAMEBUFFERCONTENTS__________ + * ' `--\____/-<\/\/\>-' + */ + hpad = max(0, mode->hsync_start - hwm->ref_hfp - w); + hpad = min(hpad, hwm->total_cols - w); + vpad = max(0, ((mode->vsync_start - hwm->ref_vfp) >> mode_ilaced) - h); + vpad = min(vpad, hwm->rows_per_field - h); /* Configure the hardware */ VEC_WRITE(VEC_APB_TIMEOUT, 0x38); @@ -347,18 +385,18 @@ void rp1vec_hw_setup(struct rp1_vec *vec BITS(VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1, h - 1)); VEC_WRITE(VEC_YUV_SCALING, hwm->yuv_scaling); VEC_WRITE(VEC_BACK_PORCH, - BITS(VEC_BACK_PORCH_HBP_MINUS1, (hwm->total_cols - w - 1) >> 1) | - BITS(VEC_BACK_PORCH_VBP_MINUS1, (hwm->rows_per_field - h - 1) >> 1)); + BITS(VEC_BACK_PORCH_HBP_MINUS1, hwm->total_cols - w - hpad - 1) | + BITS(VEC_BACK_PORCH_VBP_MINUS1, hwm->rows_per_field - h - vpad - 1)); VEC_WRITE(VEC_FRONT_PORCH, - BITS(VEC_FRONT_PORCH_HFP_MINUS1, (hwm->total_cols - w - 2) >> 1) | - BITS(VEC_FRONT_PORCH_VFP_MINUS1, (hwm->rows_per_field - h - 2) >> 1)); + BITS(VEC_FRONT_PORCH_HFP_MINUS1, hpad - 1) | + BITS(VEC_FRONT_PORCH_VFP_MINUS1, vpad - 1)); VEC_WRITE(VEC_MODE, BITS(VEC_MODE_HIGH_WATER, 0xE0) | BITS(VEC_MODE_ALIGN16, !((w | mode->hdisplay) & 15)) | - BITS(VEC_MODE_VFP_EN, (hwm->rows_per_field > h + 1)) | - BITS(VEC_MODE_VBP_EN, (hwm->rows_per_field > h)) | - BITS(VEC_MODE_HFP_EN, (hwm->total_cols > w + 1)) | - BITS(VEC_MODE_HBP_EN, (hwm->total_cols > w)) | + BITS(VEC_MODE_VFP_EN, (vpad > 0)) | + BITS(VEC_MODE_VBP_EN, (hwm->rows_per_field > h + vpad)) | + BITS(VEC_MODE_HFP_EN, (hpad > 0)) | + BITS(VEC_MODE_HBP_EN, (hwm->total_cols > w + hpad)) | BITS(VEC_MODE_FIELDS_PER_FRAME_MINUS1, hwm->interlaced) | BITS(VEC_MODE_FIRST_FIELD_ODD, hwm->first_field_odd)); for (i = 0; i < ARRAY_SIZE(hwm->back_end_regs); ++i) {