Skip to content

Commit

Permalink
drm: rp1: rp1-vec: Allow non-standard modes with various crops
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
njhollinghurst committed Jan 18, 2024
1 parent 833881c commit 04b88e1
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 44 deletions.
66 changes: 40 additions & 26 deletions drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
#include <linux/list.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/printk.h>
#include <linux/console.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
Expand Down Expand Up @@ -208,26 +207,26 @@ static void rp1vec_connector_destroy(struct drm_connector *connector)
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)
}
};
Expand Down Expand Up @@ -298,27 +297,42 @@ static enum drm_mode_status rp1vec_mode_valid(struct drm_device *dev,
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);
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;

for (i = 0; i < ARRAY_SIZE(rp1vec_modes); i++) {
const struct drm_display_mode *ref = rp1vec_modes + i;

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;
}

Expand Down Expand Up @@ -440,7 +454,7 @@ static int rp1vec_platform_probe(struct platform_device *pdev)
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;
Expand Down
74 changes: 56 additions & 18 deletions drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/printk.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
Expand Down Expand Up @@ -82,13 +81,13 @@ static const struct rp1vec_ipixfmt my_formats[] = {
/*
* 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 */
Expand All @@ -103,6 +102,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
{
.total_cols = 724,
.rows_per_field = 240,
.ref_hfp = 12,
.ref_vfp = 2,
.interlaced = false,
.first_field_odd = false,
.yuv_scaling = 0x1071d0cf,
Expand All @@ -118,6 +119,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
}, {
.total_cols = 815,
.rows_per_field = 240,
.ref_hfp = 16,
.ref_vfp = 2,
.interlaced = false,
.first_field_odd = false,
.yuv_scaling = 0x1c131962,
Expand All @@ -135,6 +138,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
{
.total_cols = 724,
.rows_per_field = 243,
.ref_hfp = 12,
.ref_vfp = 3,
.interlaced = true,
.first_field_odd = true,
.yuv_scaling = 0x1071d0cf,
Expand All @@ -150,6 +155,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
}, {
.total_cols = 815,
.rows_per_field = 243,
.ref_hfp = 16,
.ref_vfp = 3,
.interlaced = true,
.first_field_odd = true,
.yuv_scaling = 0x1c131962,
Expand All @@ -170,6 +177,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
{
.total_cols = 724,
.rows_per_field = 288,
.ref_hfp = 16,
.ref_vfp = 2,
.interlaced = false,
.first_field_odd = false,
.yuv_scaling = 0x11c1f8e0,
Expand All @@ -185,6 +194,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
}, {
.total_cols = 804,
.rows_per_field = 288,
.ref_hfp = 24,
.ref_vfp = 2,
.interlaced = false,
.first_field_odd = false,
.yuv_scaling = 0x1e635d7f,
Expand All @@ -202,6 +213,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
{
.total_cols = 724,
.rows_per_field = 288,
.ref_hfp = 16,
.ref_vfp = 5,
.interlaced = true,
.first_field_odd = false,
.yuv_scaling = 0x11c1f8e0,
Expand All @@ -217,6 +230,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
}, {
.total_cols = 804,
.rows_per_field = 288,
.ref_hfp = 24,
.ref_vfp = 5,
.interlaced = true,
.first_field_odd = false,
.yuv_scaling = 0x1e635d7f,
Expand All @@ -237,6 +252,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
{
.total_cols = 724,
.rows_per_field = 240,
.ref_hfp = 12,
.ref_vfp = 2,
.interlaced = false,
.first_field_odd = false,
.yuv_scaling = 0x11c1f8e0,
Expand All @@ -252,6 +269,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
}, {
.total_cols = 815,
.rows_per_field = 240,
.ref_hfp = 16,
.ref_vfp = 2,
.interlaced = false,
.first_field_odd = false,
.yuv_scaling = 0x1e635d7f,
Expand All @@ -269,6 +288,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
{
.total_cols = 724,
.rows_per_field = 243,
.ref_hfp = 12,
.ref_vfp = 3,
.interlaced = true,
.first_field_odd = true,
.yuv_scaling = 0x11c1f8e0,
Expand All @@ -284,6 +305,8 @@ static const struct rp1vec_hwmode rp1vec_hwmodes[3][2][2] = {
}, {
.total_cols = 815,
.rows_per_field = 243,
.ref_hfp = 16,
.ref_vfp = 3,
.interlaced = true,
.first_field_odd = true,
.yuv_scaling = 0x1e635d7f,
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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) {
Expand Down

0 comments on commit 04b88e1

Please sign in to comment.