|
|
|
@@ -116,6 +116,482 @@ void drm_mode_probed_add(struct drm_connector *connector,
|
|
|
|
|
}
|
|
|
|
|
EXPORT_SYMBOL(drm_mode_probed_add);
|
|
|
|
|
|
|
|
|
|
enum drm_mode_analog {
|
|
|
|
|
DRM_MODE_ANALOG_NTSC, /* 525 lines, 60Hz */
|
|
|
|
|
DRM_MODE_ANALOG_PAL, /* 625 lines, 50Hz */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The timings come from:
|
|
|
|
|
* - https://web.archive.org/web/20220406232708/http://www.kolumbus.fi/pami1/video/pal_ntsc.html
|
|
|
|
|
* - https://web.archive.org/web/20220406124914/http://martin.hinner.info/vga/pal.html
|
|
|
|
|
* - https://web.archive.org/web/20220609202433/http://www.batsocks.co.uk/readme/video_timing.htm
|
|
|
|
|
*/
|
|
|
|
|
#define NTSC_LINE_DURATION_NS 63556U
|
|
|
|
|
#define NTSC_LINES_NUMBER 525
|
|
|
|
|
|
|
|
|
|
#define NTSC_HBLK_DURATION_TYP_NS 10900U
|
|
|
|
|
#define NTSC_HBLK_DURATION_MIN_NS (NTSC_HBLK_DURATION_TYP_NS - 200)
|
|
|
|
|
#define NTSC_HBLK_DURATION_MAX_NS (NTSC_HBLK_DURATION_TYP_NS + 200)
|
|
|
|
|
|
|
|
|
|
#define NTSC_HACT_DURATION_TYP_NS (NTSC_LINE_DURATION_NS - NTSC_HBLK_DURATION_TYP_NS)
|
|
|
|
|
#define NTSC_HACT_DURATION_MIN_NS (NTSC_LINE_DURATION_NS - NTSC_HBLK_DURATION_MAX_NS)
|
|
|
|
|
#define NTSC_HACT_DURATION_MAX_NS (NTSC_LINE_DURATION_NS - NTSC_HBLK_DURATION_MIN_NS)
|
|
|
|
|
|
|
|
|
|
#define NTSC_HFP_DURATION_TYP_NS 1500
|
|
|
|
|
#define NTSC_HFP_DURATION_MIN_NS 1270
|
|
|
|
|
#define NTSC_HFP_DURATION_MAX_NS 2220
|
|
|
|
|
|
|
|
|
|
#define NTSC_HSLEN_DURATION_TYP_NS 4700
|
|
|
|
|
#define NTSC_HSLEN_DURATION_MIN_NS (NTSC_HSLEN_DURATION_TYP_NS - 100)
|
|
|
|
|
#define NTSC_HSLEN_DURATION_MAX_NS (NTSC_HSLEN_DURATION_TYP_NS + 100)
|
|
|
|
|
|
|
|
|
|
#define NTSC_HBP_DURATION_TYP_NS 4700
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* I couldn't find the actual tolerance for the back porch, so let's
|
|
|
|
|
* just reuse the sync length ones.
|
|
|
|
|
*/
|
|
|
|
|
#define NTSC_HBP_DURATION_MIN_NS (NTSC_HBP_DURATION_TYP_NS - 100)
|
|
|
|
|
#define NTSC_HBP_DURATION_MAX_NS (NTSC_HBP_DURATION_TYP_NS + 100)
|
|
|
|
|
|
|
|
|
|
#define PAL_LINE_DURATION_NS 64000U
|
|
|
|
|
#define PAL_LINES_NUMBER 625
|
|
|
|
|
|
|
|
|
|
#define PAL_HACT_DURATION_TYP_NS 51950U
|
|
|
|
|
#define PAL_HACT_DURATION_MIN_NS (PAL_HACT_DURATION_TYP_NS - 100)
|
|
|
|
|
#define PAL_HACT_DURATION_MAX_NS (PAL_HACT_DURATION_TYP_NS + 400)
|
|
|
|
|
|
|
|
|
|
#define PAL_HBLK_DURATION_TYP_NS (PAL_LINE_DURATION_NS - PAL_HACT_DURATION_TYP_NS)
|
|
|
|
|
#define PAL_HBLK_DURATION_MIN_NS (PAL_LINE_DURATION_NS - PAL_HACT_DURATION_MAX_NS)
|
|
|
|
|
#define PAL_HBLK_DURATION_MAX_NS (PAL_LINE_DURATION_NS - PAL_HACT_DURATION_MIN_NS)
|
|
|
|
|
|
|
|
|
|
#define PAL_HFP_DURATION_TYP_NS 1650
|
|
|
|
|
#define PAL_HFP_DURATION_MIN_NS (PAL_HFP_DURATION_TYP_NS - 100)
|
|
|
|
|
#define PAL_HFP_DURATION_MAX_NS (PAL_HFP_DURATION_TYP_NS + 400)
|
|
|
|
|
|
|
|
|
|
#define PAL_HSLEN_DURATION_TYP_NS 4700
|
|
|
|
|
#define PAL_HSLEN_DURATION_MIN_NS (PAL_HSLEN_DURATION_TYP_NS - 200)
|
|
|
|
|
#define PAL_HSLEN_DURATION_MAX_NS (PAL_HSLEN_DURATION_TYP_NS + 200)
|
|
|
|
|
|
|
|
|
|
#define PAL_HBP_DURATION_TYP_NS 5700
|
|
|
|
|
#define PAL_HBP_DURATION_MIN_NS (PAL_HBP_DURATION_TYP_NS - 200)
|
|
|
|
|
#define PAL_HBP_DURATION_MAX_NS (PAL_HBP_DURATION_TYP_NS + 200)
|
|
|
|
|
|
|
|
|
|
struct analog_param_field {
|
|
|
|
|
unsigned int even, odd;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define PARAM_FIELD(_odd, _even) \
|
|
|
|
|
{ .even = _even, .odd = _odd }
|
|
|
|
|
|
|
|
|
|
struct analog_param_range {
|
|
|
|
|
unsigned int min, typ, max;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define PARAM_RANGE(_min, _typ, _max) \
|
|
|
|
|
{ .min = _min, .typ = _typ, .max = _max }
|
|
|
|
|
|
|
|
|
|
struct analog_parameters {
|
|
|
|
|
unsigned int num_lines;
|
|
|
|
|
unsigned int line_duration_ns;
|
|
|
|
|
|
|
|
|
|
struct analog_param_range hact_ns;
|
|
|
|
|
struct analog_param_range hfp_ns;
|
|
|
|
|
struct analog_param_range hslen_ns;
|
|
|
|
|
struct analog_param_range hbp_ns;
|
|
|
|
|
struct analog_param_range hblk_ns;
|
|
|
|
|
|
|
|
|
|
unsigned int bt601_hfp;
|
|
|
|
|
|
|
|
|
|
struct analog_param_field vfp_lines;
|
|
|
|
|
struct analog_param_field vslen_lines;
|
|
|
|
|
struct analog_param_field vbp_lines;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define TV_MODE_PARAMETER(_mode, _lines, _line_dur, _hact, _hfp, \
|
|
|
|
|
_hslen, _hbp, _hblk, _bt601_hfp, _vfp, \
|
|
|
|
|
_vslen, _vbp) \
|
|
|
|
|
[_mode] = { \
|
|
|
|
|
.num_lines = _lines, \
|
|
|
|
|
.line_duration_ns = _line_dur, \
|
|
|
|
|
.hact_ns = _hact, \
|
|
|
|
|
.hfp_ns = _hfp, \
|
|
|
|
|
.hslen_ns = _hslen, \
|
|
|
|
|
.hbp_ns = _hbp, \
|
|
|
|
|
.hblk_ns = _hblk, \
|
|
|
|
|
.bt601_hfp = _bt601_hfp, \
|
|
|
|
|
.vfp_lines = _vfp, \
|
|
|
|
|
.vslen_lines = _vslen, \
|
|
|
|
|
.vbp_lines = _vbp, \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct analog_parameters tv_modes_parameters[] = {
|
|
|
|
|
TV_MODE_PARAMETER(DRM_MODE_ANALOG_NTSC,
|
|
|
|
|
NTSC_LINES_NUMBER,
|
|
|
|
|
NTSC_LINE_DURATION_NS,
|
|
|
|
|
PARAM_RANGE(NTSC_HACT_DURATION_MIN_NS,
|
|
|
|
|
NTSC_HACT_DURATION_TYP_NS,
|
|
|
|
|
NTSC_HACT_DURATION_MAX_NS),
|
|
|
|
|
PARAM_RANGE(NTSC_HFP_DURATION_MIN_NS,
|
|
|
|
|
NTSC_HFP_DURATION_TYP_NS,
|
|
|
|
|
NTSC_HFP_DURATION_MAX_NS),
|
|
|
|
|
PARAM_RANGE(NTSC_HSLEN_DURATION_MIN_NS,
|
|
|
|
|
NTSC_HSLEN_DURATION_TYP_NS,
|
|
|
|
|
NTSC_HSLEN_DURATION_MAX_NS),
|
|
|
|
|
PARAM_RANGE(NTSC_HBP_DURATION_MIN_NS,
|
|
|
|
|
NTSC_HBP_DURATION_TYP_NS,
|
|
|
|
|
NTSC_HBP_DURATION_MAX_NS),
|
|
|
|
|
PARAM_RANGE(NTSC_HBLK_DURATION_MIN_NS,
|
|
|
|
|
NTSC_HBLK_DURATION_TYP_NS,
|
|
|
|
|
NTSC_HBLK_DURATION_MAX_NS),
|
|
|
|
|
16,
|
|
|
|
|
PARAM_FIELD(3, 3),
|
|
|
|
|
PARAM_FIELD(3, 3),
|
|
|
|
|
PARAM_FIELD(16, 17)),
|
|
|
|
|
TV_MODE_PARAMETER(DRM_MODE_ANALOG_PAL,
|
|
|
|
|
PAL_LINES_NUMBER,
|
|
|
|
|
PAL_LINE_DURATION_NS,
|
|
|
|
|
PARAM_RANGE(PAL_HACT_DURATION_MIN_NS,
|
|
|
|
|
PAL_HACT_DURATION_TYP_NS,
|
|
|
|
|
PAL_HACT_DURATION_MAX_NS),
|
|
|
|
|
PARAM_RANGE(PAL_HFP_DURATION_MIN_NS,
|
|
|
|
|
PAL_HFP_DURATION_TYP_NS,
|
|
|
|
|
PAL_HFP_DURATION_MAX_NS),
|
|
|
|
|
PARAM_RANGE(PAL_HSLEN_DURATION_MIN_NS,
|
|
|
|
|
PAL_HSLEN_DURATION_TYP_NS,
|
|
|
|
|
PAL_HSLEN_DURATION_MAX_NS),
|
|
|
|
|
PARAM_RANGE(PAL_HBP_DURATION_MIN_NS,
|
|
|
|
|
PAL_HBP_DURATION_TYP_NS,
|
|
|
|
|
PAL_HBP_DURATION_MAX_NS),
|
|
|
|
|
PARAM_RANGE(PAL_HBLK_DURATION_MIN_NS,
|
|
|
|
|
PAL_HBLK_DURATION_TYP_NS,
|
|
|
|
|
PAL_HBLK_DURATION_MAX_NS),
|
|
|
|
|
12,
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The front porch is actually 6 short sync
|
|
|
|
|
* pulses for the even field, and 5 for the
|
|
|
|
|
* odd field. Each sync takes half a life so
|
|
|
|
|
* the odd field front porch is shorter by
|
|
|
|
|
* half a line.
|
|
|
|
|
*
|
|
|
|
|
* In progressive, we're supposed to use 6
|
|
|
|
|
* pulses, so we're fine there
|
|
|
|
|
*/
|
|
|
|
|
PARAM_FIELD(3, 2),
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The vsync length is 5 long sync pulses,
|
|
|
|
|
* each field taking half a line. We're
|
|
|
|
|
* shorter for both fields by half a line.
|
|
|
|
|
*
|
|
|
|
|
* In progressive, we're supposed to use 5
|
|
|
|
|
* pulses, so we're off by half
|
|
|
|
|
* a line.
|
|
|
|
|
*
|
|
|
|
|
* In interlace, we're now off by half a line
|
|
|
|
|
* for the even field and one line for the odd
|
|
|
|
|
* field.
|
|
|
|
|
*/
|
|
|
|
|
PARAM_FIELD(3, 3),
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The back porch starts with post-equalizing
|
|
|
|
|
* pulses, consisting in 5 short sync pulses
|
|
|
|
|
* for the even field, 4 for the odd field. In
|
|
|
|
|
* progressive, it's 5 short syncs.
|
|
|
|
|
*
|
|
|
|
|
* In progressive, we thus have 2.5 lines,
|
|
|
|
|
* plus the 0.5 line we were missing
|
|
|
|
|
* previously, so we should use 3 lines.
|
|
|
|
|
*
|
|
|
|
|
* In interlace, the even field is in the
|
|
|
|
|
* exact same case than progressive. For the
|
|
|
|
|
* odd field, we should be using 2 lines but
|
|
|
|
|
* we're one line short, so we'll make up for
|
|
|
|
|
* it here by using 3.
|
|
|
|
|
*
|
|
|
|
|
* The entire blanking area is supposed to
|
|
|
|
|
* take 25 lines, so we also need to account
|
|
|
|
|
* for the rest of the blanking area that
|
|
|
|
|
* can't be in either the front porch or sync
|
|
|
|
|
* period.
|
|
|
|
|
*/
|
|
|
|
|
PARAM_FIELD(19, 20)),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static int fill_analog_mode(struct drm_device *dev,
|
|
|
|
|
struct drm_display_mode *mode,
|
|
|
|
|
const struct analog_parameters *params,
|
|
|
|
|
unsigned long pixel_clock_hz,
|
|
|
|
|
unsigned int hactive,
|
|
|
|
|
unsigned int vactive,
|
|
|
|
|
bool interlace)
|
|
|
|
|
{
|
|
|
|
|
unsigned long pixel_duration_ns = NSEC_PER_SEC / pixel_clock_hz;
|
|
|
|
|
unsigned int htotal, vtotal;
|
|
|
|
|
unsigned int max_hact, hact_duration_ns;
|
|
|
|
|
unsigned int hblk, hblk_duration_ns;
|
|
|
|
|
unsigned int hfp, hfp_duration_ns;
|
|
|
|
|
unsigned int hslen, hslen_duration_ns;
|
|
|
|
|
unsigned int hbp, hbp_duration_ns;
|
|
|
|
|
unsigned int porches, porches_duration_ns;
|
|
|
|
|
unsigned int vfp, vfp_min;
|
|
|
|
|
unsigned int vbp, vbp_min;
|
|
|
|
|
unsigned int vslen;
|
|
|
|
|
bool bt601 = false;
|
|
|
|
|
int porches_rem;
|
|
|
|
|
u64 result;
|
|
|
|
|
|
|
|
|
|
drm_dbg_kms(dev,
|
|
|
|
|
"Generating a %ux%u%c, %u-line mode with a %lu kHz clock\n",
|
|
|
|
|
hactive, vactive,
|
|
|
|
|
interlace ? 'i' : 'p',
|
|
|
|
|
params->num_lines,
|
|
|
|
|
pixel_clock_hz / 1000);
|
|
|
|
|
|
|
|
|
|
max_hact = params->hact_ns.max / pixel_duration_ns;
|
|
|
|
|
if (pixel_clock_hz == 13500000 && hactive > max_hact && hactive <= 720) {
|
|
|
|
|
drm_dbg_kms(dev, "Trying to generate a BT.601 mode. Disabling checks.\n");
|
|
|
|
|
bt601 = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Our pixel duration is going to be round down by the division,
|
|
|
|
|
* so rounding up is probably going to introduce even more
|
|
|
|
|
* deviation.
|
|
|
|
|
*/
|
|
|
|
|
result = (u64)params->line_duration_ns * pixel_clock_hz;
|
|
|
|
|
do_div(result, NSEC_PER_SEC);
|
|
|
|
|
htotal = result;
|
|
|
|
|
|
|
|
|
|
drm_dbg_kms(dev, "Total Horizontal Number of Pixels: %u\n", htotal);
|
|
|
|
|
|
|
|
|
|
hact_duration_ns = hactive * pixel_duration_ns;
|
|
|
|
|
if (!bt601 &&
|
|
|
|
|
(hact_duration_ns < params->hact_ns.min ||
|
|
|
|
|
hact_duration_ns > params->hact_ns.max)) {
|
|
|
|
|
DRM_ERROR("Invalid horizontal active area duration: %uns (min: %u, max %u)\n",
|
|
|
|
|
hact_duration_ns, params->hact_ns.min, params->hact_ns.max);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hblk = htotal - hactive;
|
|
|
|
|
drm_dbg_kms(dev, "Horizontal Blanking Period: %u\n", hblk);
|
|
|
|
|
|
|
|
|
|
hblk_duration_ns = hblk * pixel_duration_ns;
|
|
|
|
|
if (!bt601 &&
|
|
|
|
|
(hblk_duration_ns < params->hblk_ns.min ||
|
|
|
|
|
hblk_duration_ns > params->hblk_ns.max)) {
|
|
|
|
|
DRM_ERROR("Invalid horizontal blanking duration: %uns (min: %u, max %u)\n",
|
|
|
|
|
hblk_duration_ns, params->hblk_ns.min, params->hblk_ns.max);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hslen = DIV_ROUND_UP(params->hslen_ns.typ, pixel_duration_ns);
|
|
|
|
|
drm_dbg_kms(dev, "Horizontal Sync Period: %u\n", hslen);
|
|
|
|
|
|
|
|
|
|
hslen_duration_ns = hslen * pixel_duration_ns;
|
|
|
|
|
if (!bt601 &&
|
|
|
|
|
(hslen_duration_ns < params->hslen_ns.min ||
|
|
|
|
|
hslen_duration_ns > params->hslen_ns.max)) {
|
|
|
|
|
DRM_ERROR("Invalid horizontal sync duration: %uns (min: %u, max %u)\n",
|
|
|
|
|
hslen_duration_ns, params->hslen_ns.min, params->hslen_ns.max);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
porches = hblk - hslen;
|
|
|
|
|
drm_dbg_kms(dev, "Remaining horizontal pixels for both porches: %u\n", porches);
|
|
|
|
|
|
|
|
|
|
porches_duration_ns = porches * pixel_duration_ns;
|
|
|
|
|
if (!bt601 &&
|
|
|
|
|
(porches_duration_ns > (params->hfp_ns.max + params->hbp_ns.max) ||
|
|
|
|
|
porches_duration_ns < (params->hfp_ns.min + params->hbp_ns.min))) {
|
|
|
|
|
DRM_ERROR("Invalid horizontal porches duration: %uns\n", porches_duration_ns);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bt601) {
|
|
|
|
|
hfp = params->bt601_hfp;
|
|
|
|
|
} else {
|
|
|
|
|
unsigned int hfp_min = DIV_ROUND_UP(params->hfp_ns.min,
|
|
|
|
|
pixel_duration_ns);
|
|
|
|
|
unsigned int hbp_min = DIV_ROUND_UP(params->hbp_ns.min,
|
|
|
|
|
pixel_duration_ns);
|
|
|
|
|
int porches_rem = porches - hfp_min - hbp_min;
|
|
|
|
|
|
|
|
|
|
hfp = hfp_min + DIV_ROUND_UP(porches_rem, 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drm_dbg_kms(dev, "Horizontal Front Porch: %u\n", hfp);
|
|
|
|
|
|
|
|
|
|
hfp_duration_ns = hfp * pixel_duration_ns;
|
|
|
|
|
if (!bt601 &&
|
|
|
|
|
(hfp_duration_ns < params->hfp_ns.min ||
|
|
|
|
|
hfp_duration_ns > params->hfp_ns.max)) {
|
|
|
|
|
DRM_ERROR("Invalid horizontal front porch duration: %uns (min: %u, max %u)\n",
|
|
|
|
|
hfp_duration_ns, params->hfp_ns.min, params->hfp_ns.max);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hbp = porches - hfp;
|
|
|
|
|
drm_dbg_kms(dev, "Horizontal Back Porch: %u\n", hbp);
|
|
|
|
|
|
|
|
|
|
hbp_duration_ns = hbp * pixel_duration_ns;
|
|
|
|
|
if (!bt601 &&
|
|
|
|
|
(hbp_duration_ns < params->hbp_ns.min ||
|
|
|
|
|
hbp_duration_ns > params->hbp_ns.max)) {
|
|
|
|
|
DRM_ERROR("Invalid horizontal back porch duration: %uns (min: %u, max %u)\n",
|
|
|
|
|
hbp_duration_ns, params->hbp_ns.min, params->hbp_ns.max);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (htotal != (hactive + hfp + hslen + hbp))
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
mode->clock = pixel_clock_hz / 1000;
|
|
|
|
|
mode->hdisplay = hactive;
|
|
|
|
|
mode->hsync_start = mode->hdisplay + hfp;
|
|
|
|
|
mode->hsync_end = mode->hsync_start + hslen;
|
|
|
|
|
mode->htotal = mode->hsync_end + hbp;
|
|
|
|
|
|
|
|
|
|
if (interlace) {
|
|
|
|
|
vfp_min = params->vfp_lines.even + params->vfp_lines.odd;
|
|
|
|
|
vbp_min = params->vbp_lines.even + params->vbp_lines.odd;
|
|
|
|
|
vslen = params->vslen_lines.even + params->vslen_lines.odd;
|
|
|
|
|
} else {
|
|
|
|
|
/*
|
|
|
|
|
* By convention, NTSC (aka 525/60) systems start with
|
|
|
|
|
* the even field, but PAL (aka 625/50) systems start
|
|
|
|
|
* with the odd one.
|
|
|
|
|
*
|
|
|
|
|
* PAL systems also have asymmetric timings between the
|
|
|
|
|
* even and odd field, while NTSC is symmetric.
|
|
|
|
|
*
|
|
|
|
|
* Moreover, if we want to create a progressive mode for
|
|
|
|
|
* PAL, we need to use the odd field timings.
|
|
|
|
|
*
|
|
|
|
|
* Since odd == even for NTSC, we can just use the odd
|
|
|
|
|
* one all the time to simplify the code a bit.
|
|
|
|
|
*/
|
|
|
|
|
vfp_min = params->vfp_lines.odd;
|
|
|
|
|
vbp_min = params->vbp_lines.odd;
|
|
|
|
|
vslen = params->vslen_lines.odd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drm_dbg_kms(dev, "Vertical Sync Period: %u\n", vslen);
|
|
|
|
|
|
|
|
|
|
porches = params->num_lines - vactive - vslen;
|
|
|
|
|
drm_dbg_kms(dev, "Remaining vertical pixels for both porches: %u\n", porches);
|
|
|
|
|
|
|
|
|
|
porches_rem = porches - vfp_min - vbp_min;
|
|
|
|
|
vfp = vfp_min + (porches_rem / 2);
|
|
|
|
|
drm_dbg_kms(dev, "Vertical Front Porch: %u\n", vfp);
|
|
|
|
|
|
|
|
|
|
vbp = porches - vfp;
|
|
|
|
|
drm_dbg_kms(dev, "Vertical Back Porch: %u\n", vbp);
|
|
|
|
|
|
|
|
|
|
vtotal = vactive + vfp + vslen + vbp;
|
|
|
|
|
if (params->num_lines != vtotal) {
|
|
|
|
|
DRM_ERROR("Invalid vertical total: %upx (expected %upx)\n",
|
|
|
|
|
vtotal, params->num_lines);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mode->vdisplay = vactive;
|
|
|
|
|
mode->vsync_start = mode->vdisplay + vfp;
|
|
|
|
|
mode->vsync_end = mode->vsync_start + vslen;
|
|
|
|
|
mode->vtotal = mode->vsync_end + vbp;
|
|
|
|
|
|
|
|
|
|
if (mode->vtotal != params->num_lines)
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
mode->type = DRM_MODE_TYPE_DRIVER;
|
|
|
|
|
mode->flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC;
|
|
|
|
|
if (interlace)
|
|
|
|
|
mode->flags |= DRM_MODE_FLAG_INTERLACE;
|
|
|
|
|
|
|
|
|
|
drm_mode_set_name(mode);
|
|
|
|
|
|
|
|
|
|
drm_dbg_kms(dev, "Generated mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(mode));
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* drm_analog_tv_mode - create a display mode for an analog TV
|
|
|
|
|
* @dev: drm device
|
|
|
|
|
* @tv_mode: TV Mode standard to create a mode for. See DRM_MODE_TV_MODE_*.
|
|
|
|
|
* @pixel_clock_hz: Pixel Clock Frequency, in Hertz
|
|
|
|
|
* @hdisplay: hdisplay size
|
|
|
|
|
* @vdisplay: vdisplay size
|
|
|
|
|
* @interlace: whether to compute an interlaced mode
|
|
|
|
|
*
|
|
|
|
|
* This function creates a struct drm_display_mode instance suited for
|
|
|
|
|
* an analog TV output, for one of the usual analog TV mode.
|
|
|
|
|
*
|
|
|
|
|
* Note that @hdisplay is larger than the usual constraints for the PAL
|
|
|
|
|
* and NTSC timings, and we'll choose to ignore most timings constraints
|
|
|
|
|
* to reach those resolutions.
|
|
|
|
|
*
|
|
|
|
|
* Returns:
|
|
|
|
|
*
|
|
|
|
|
* A pointer to the mode, allocated with drm_mode_create(). Returns NULL
|
|
|
|
|
* on error.
|
|
|
|
|
*/
|
|
|
|
|
struct drm_display_mode *drm_analog_tv_mode(struct drm_device *dev,
|
|
|
|
|
enum drm_connector_tv_mode tv_mode,
|
|
|
|
|
unsigned long pixel_clock_hz,
|
|
|
|
|
unsigned int hdisplay,
|
|
|
|
|
unsigned int vdisplay,
|
|
|
|
|
bool interlace)
|
|
|
|
|
{
|
|
|
|
|
struct drm_display_mode *mode;
|
|
|
|
|
enum drm_mode_analog analog;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
switch (tv_mode) {
|
|
|
|
|
case DRM_MODE_TV_MODE_NTSC:
|
|
|
|
|
fallthrough;
|
|
|
|
|
case DRM_MODE_TV_MODE_NTSC_443:
|
|
|
|
|
fallthrough;
|
|
|
|
|
case DRM_MODE_TV_MODE_NTSC_J:
|
|
|
|
|
fallthrough;
|
|
|
|
|
case DRM_MODE_TV_MODE_PAL_M:
|
|
|
|
|
analog = DRM_MODE_ANALOG_NTSC;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DRM_MODE_TV_MODE_PAL:
|
|
|
|
|
fallthrough;
|
|
|
|
|
case DRM_MODE_TV_MODE_PAL_N:
|
|
|
|
|
fallthrough;
|
|
|
|
|
case DRM_MODE_TV_MODE_SECAM:
|
|
|
|
|
analog = DRM_MODE_ANALOG_PAL;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mode = drm_mode_create(dev);
|
|
|
|
|
if (!mode)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
ret = fill_analog_mode(dev, mode,
|
|
|
|
|
&tv_modes_parameters[analog],
|
|
|
|
|
pixel_clock_hz, hdisplay, vdisplay, interlace);
|
|
|
|
|
if (ret)
|
|
|
|
|
goto err_free_mode;
|
|
|
|
|
|
|
|
|
|
return mode;
|
|
|
|
|
|
|
|
|
|
err_free_mode:
|
|
|
|
|
drm_mode_destroy(dev, mode);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
EXPORT_SYMBOL(drm_analog_tv_mode);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* drm_cvt_mode -create a modeline based on the CVT algorithm
|
|
|
|
|
* @dev: drm device
|
|
|
|
|