diff options
Diffstat (limited to 'drivers/gpu/drm/drm_modes.c')
| -rw-r--r-- | drivers/gpu/drm/drm_modes.c | 1437 |
1 files changed, 1201 insertions, 236 deletions
diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index 24a750436559..e72f855fc495 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -30,14 +30,22 @@ * authorization from the copyright holder(s) and author(s). */ +#include <linux/ctype.h> +#include <linux/export.h> +#include <linux/fb.h> /* for KHZ2PICOS() */ #include <linux/list.h> #include <linux/list_sort.h> -#include <linux/export.h> -#include <drm/drmP.h> -#include <drm/drm_crtc.h> +#include <linux/of.h> + +#include <video/of_display_timing.h> #include <video/of_videomode.h> #include <video/videomode.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_edid.h> #include <drm/drm_modes.h> +#include <drm/drm_print.h> #include "drm_crtc_internal.h" @@ -71,11 +79,6 @@ struct drm_display_mode *drm_mode_create(struct drm_device *dev) if (!nmode) return NULL; - if (drm_mode_object_add(dev, &nmode->base, DRM_MODE_OBJECT_MODE)) { - kfree(nmode); - return NULL; - } - return nmode; } EXPORT_SYMBOL(drm_mode_create); @@ -92,8 +95,6 @@ void drm_mode_destroy(struct drm_device *dev, struct drm_display_mode *mode) if (!mode) return; - drm_mode_object_unregister(dev, &mode->base); - kfree(mode); } EXPORT_SYMBOL(drm_mode_destroy); @@ -116,6 +117,485 @@ 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_err(dev, "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_err(dev, "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_err(dev, "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_err(dev, "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_err(dev, "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_err(dev, "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_err(dev, "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 modes. Where + * this is DRM_MODE_TV_MODE_MONOCHROME, a 625-line mode will be created. + * + * 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: + fallthrough; + case DRM_MODE_TV_MODE_MONOCHROME: + 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 @@ -130,7 +610,7 @@ EXPORT_SYMBOL(drm_mode_probed_add); * according to the hdisplay, vdisplay, vrefresh. * It is based from the VESA(TM) Coordinated Video Timing Generator by * Graham Loveridge April 9, 2003 available at - * http://www.elo.utfsm.cl/~elo212/docs/CVTd6r1.xls + * http://www.elo.utfsm.cl/~elo212/docs/CVTd6r1.xls * * And it is copied from xf86CVTmode in xserver/hw/xfree86/modes/xf86cvt.c. * What I have done is to translate it by using integer calculation. @@ -161,6 +641,9 @@ struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay, int interlace; u64 tmp; + if (!hdisplay || !vdisplay) + return NULL; + /* allocate the drm_display_mode structure. If failure, we will * return directly */ @@ -233,7 +716,7 @@ struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay, /* 3) Nominal HSync width (% of line period) - default 8 */ #define CVT_HSYNC_PERCENTAGE 8 unsigned int hblank_percentage; - int vsyncandback_porch, vback_porch, hblank; + int vsyncandback_porch, __maybe_unused vback_porch, hblank; /* estimated the horizontal period */ tmp1 = HV_FACTOR * 1000000 - @@ -386,15 +869,19 @@ drm_gtf_mode_complex(struct drm_device *dev, int hdisplay, int vdisplay, int top_margin, bottom_margin; int interlace; unsigned int hfreq_est; - int vsync_plus_bp, vback_porch; - unsigned int vtotal_lines, vfieldrate_est, hperiod; - unsigned int vfield_rate, vframe_rate; + int vsync_plus_bp, __maybe_unused vback_porch; + unsigned int vtotal_lines, __maybe_unused vfieldrate_est; + unsigned int __maybe_unused hperiod; + unsigned int vfield_rate, __maybe_unused vframe_rate; int left_margin, right_margin; unsigned int total_active_pixels, ideal_duty_cycle; unsigned int hblank, total_pixels, pixel_freq; int hsync, hfront_porch, vodd_front_porch_lines; unsigned int tmp1, tmp2; + if (!hdisplay || !vdisplay) + return NULL; + drm_mode = drm_mode_create(dev); if (!drm_mode) return NULL; @@ -544,7 +1031,7 @@ EXPORT_SYMBOL(drm_gtf_mode_complex); * Generalized Timing Formula is derived from: * * GTF Spreadsheet by Andy Morrish (1/5/97) - * available at http://www.vesa.org + * available at https://www.vesa.org * * And it is copied from the file of xserver/hw/xfree86/modes/xf86gtf.c. * What I have done is to translate it by using integer calculation. @@ -662,22 +1149,22 @@ EXPORT_SYMBOL_GPL(drm_display_mode_to_videomode); * @bus_flags: information about pixelclk, sync and DE polarity will be stored * here * - * Sets DRM_BUS_FLAG_DE_(LOW|HIGH), DRM_BUS_FLAG_PIXDATA_(POS|NEG)EDGE and - * DISPLAY_FLAGS_SYNC_(POS|NEG)EDGE in @bus_flags according to DISPLAY_FLAGS + * Sets DRM_BUS_FLAG_DE_(LOW|HIGH), DRM_BUS_FLAG_PIXDATA_DRIVE_(POS|NEG)EDGE + * and DISPLAY_FLAGS_SYNC_(POS|NEG)EDGE in @bus_flags according to DISPLAY_FLAGS * found in @vm */ void drm_bus_flags_from_videomode(const struct videomode *vm, u32 *bus_flags) { *bus_flags = 0; if (vm->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE) - *bus_flags |= DRM_BUS_FLAG_PIXDATA_POSEDGE; + *bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE; if (vm->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) - *bus_flags |= DRM_BUS_FLAG_PIXDATA_NEGEDGE; + *bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; if (vm->flags & DISPLAY_FLAGS_SYNC_POSEDGE) - *bus_flags |= DRM_BUS_FLAG_SYNC_POSEDGE; + *bus_flags |= DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE; if (vm->flags & DISPLAY_FLAGS_SYNC_NEGEDGE) - *bus_flags |= DRM_BUS_FLAG_SYNC_NEGEDGE; + *bus_flags |= DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE; if (vm->flags & DISPLAY_FLAGS_DE_LOW) *bus_flags |= DRM_BUS_FLAG_DE_LOW; @@ -716,13 +1203,60 @@ int of_get_drm_display_mode(struct device_node *np, if (bus_flags) drm_bus_flags_from_videomode(&vm, bus_flags); - pr_debug("%pOF: got %dx%d display mode\n", - np, vm.hactive, vm.vactive); - drm_mode_debug_printmodeline(dmode); + pr_debug("%pOF: got %dx%d display mode: " DRM_MODE_FMT "\n", + np, vm.hactive, vm.vactive, DRM_MODE_ARG(dmode)); return 0; } EXPORT_SYMBOL_GPL(of_get_drm_display_mode); + +/** + * of_get_drm_panel_display_mode - get a panel-timing drm_display_mode from devicetree + * @np: device_node with the panel-timing specification + * @dmode: will be set to the return value + * @bus_flags: information about pixelclk, sync and DE polarity + * + * The mandatory Device Tree properties width-mm and height-mm + * are read and set on the display mode. + * + * Returns: + * Zero on success, negative error code on failure. + */ +int of_get_drm_panel_display_mode(struct device_node *np, + struct drm_display_mode *dmode, u32 *bus_flags) +{ + u32 width_mm = 0, height_mm = 0; + struct display_timing timing; + struct videomode vm; + int ret; + + ret = of_get_display_timing(np, "panel-timing", &timing); + if (ret) + return ret; + + videomode_from_timing(&timing, &vm); + + memset(dmode, 0, sizeof(*dmode)); + drm_display_mode_from_videomode(&vm, dmode); + if (bus_flags) + drm_bus_flags_from_videomode(&vm, bus_flags); + + ret = of_property_read_u32(np, "width-mm", &width_mm); + if (ret) + return ret; + + ret = of_property_read_u32(np, "height-mm", &height_mm); + if (ret) + return ret; + + dmode->width_mm = width_mm; + dmode->height_mm = height_mm; + + pr_debug(DRM_MODE_FMT "\n", DRM_MODE_ARG(dmode)); + + return 0; +} +EXPORT_SYMBOL_GPL(of_get_drm_panel_display_mode); #endif /* CONFIG_OF */ #endif /* CONFIG_VIDEOMODE_HELPERS */ @@ -744,61 +1278,33 @@ void drm_mode_set_name(struct drm_display_mode *mode) EXPORT_SYMBOL(drm_mode_set_name); /** - * drm_mode_hsync - get the hsync of a mode - * @mode: mode - * - * Returns: - * @modes's hsync rate in kHz, rounded to the nearest integer. Calculates the - * value first if it is not yet set. - */ -int drm_mode_hsync(const struct drm_display_mode *mode) -{ - unsigned int calc_val; - - if (mode->hsync) - return mode->hsync; - - if (mode->htotal < 0) - return 0; - - calc_val = (mode->clock * 1000) / mode->htotal; /* hsync in Hz */ - calc_val += 500; /* round to 1000Hz */ - calc_val /= 1000; /* truncate to kHz */ - - return calc_val; -} -EXPORT_SYMBOL(drm_mode_hsync); - -/** * drm_mode_vrefresh - get the vrefresh of a mode * @mode: mode * * Returns: - * @modes's vrefresh rate in Hz, rounded to the nearest integer. Calculates the - * value first if it is not yet set. + * @modes's vrefresh rate in Hz, rounded to the nearest integer. */ int drm_mode_vrefresh(const struct drm_display_mode *mode) { - int refresh = 0; + unsigned int num = 1, den = 1; - if (mode->vrefresh > 0) - refresh = mode->vrefresh; - else if (mode->htotal > 0 && mode->vtotal > 0) { - unsigned int num, den; + if (mode->htotal == 0 || mode->vtotal == 0) + return 0; - num = mode->clock * 1000; - den = mode->htotal * mode->vtotal; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + num *= 2; + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + den *= 2; + if (mode->vscan > 1) + den *= mode->vscan; - if (mode->flags & DRM_MODE_FLAG_INTERLACE) - num *= 2; - if (mode->flags & DRM_MODE_FLAG_DBLSCAN) - den *= 2; - if (mode->vscan > 1) - den *= mode->vscan; + if (check_mul_overflow(mode->clock, num, &num)) + return 0; - refresh = DIV_ROUND_CLOSEST(num, den); - } - return refresh; + if (check_mul_overflow(mode->htotal * mode->vtotal, den, &den)) + return 0; + + return DIV_ROUND_CLOSEST_ULL(mul_u32_u32(num, 1000), den); } EXPORT_SYMBOL(drm_mode_vrefresh); @@ -814,7 +1320,9 @@ EXPORT_SYMBOL(drm_mode_vrefresh); void drm_mode_get_hv_timing(const struct drm_display_mode *mode, int *hdisplay, int *vdisplay) { - struct drm_display_mode adjusted = *mode; + struct drm_display_mode adjusted; + + drm_mode_init(&adjusted, mode); drm_mode_set_crtcinfo(&adjusted, CRTC_STEREO_DOUBLE_ONLY); *hdisplay = adjusted.crtc_hdisplay; @@ -906,21 +1414,36 @@ EXPORT_SYMBOL(drm_mode_set_crtcinfo); * @dst: mode to overwrite * @src: mode to copy * - * Copy an existing mode into another mode, preserving the object id and + * Copy an existing mode into another mode, preserving the * list head of the destination mode. */ void drm_mode_copy(struct drm_display_mode *dst, const struct drm_display_mode *src) { - int id = dst->base.id; struct list_head head = dst->head; *dst = *src; - dst->base.id = id; dst->head = head; } EXPORT_SYMBOL(drm_mode_copy); /** + * drm_mode_init - initialize the mode from another mode + * @dst: mode to overwrite + * @src: mode to copy + * + * Copy an existing mode into another mode, zeroing the + * list head of the destination mode. Typically used + * to guarantee the list head is not left with stack + * garbage in on-stack modes. + */ +void drm_mode_init(struct drm_display_mode *dst, const struct drm_display_mode *src) +{ + memset(dst, 0, sizeof(*dst)); + drm_mode_copy(dst, src); +} +EXPORT_SYMBOL(drm_mode_init); + +/** * drm_mode_duplicate - allocate and duplicate an existing mode * @dev: drm_device to allocate the duplicated mode for * @mode: mode to duplicate @@ -1204,16 +1727,11 @@ enum drm_mode_status drm_mode_validate_ycbcr420(const struct drm_display_mode *mode, struct drm_connector *connector) { - u8 vic = drm_match_cea_mode(mode); - enum drm_mode_status status = MODE_OK; - struct drm_hdmi_info *hdmi = &connector->display_info.hdmi; - - if (test_bit(vic, hdmi->y420_vdb_modes)) { - if (!connector->ycbcr_420_allowed) - status = MODE_NO_420; - } + if (!connector->ycbcr_420_allowed && + drm_mode_is_420_only(&connector->display_info, mode)) + return MODE_NO_420; - return status; + return MODE_OK; } EXPORT_SYMBOL(drm_mode_validate_ycbcr420); @@ -1281,7 +1799,7 @@ const char *drm_get_mode_status_name(enum drm_mode_status status) * @verbose: be verbose about it * * This helper function can be used to prune a display mode list after - * validation has been completed. All modes who's status is not MODE_OK will be + * validation has been completed. All modes whose status is not MODE_OK will be * removed from the list, and if @verbose the status code and mode name is also * printed to dmesg. */ @@ -1293,11 +1811,13 @@ void drm_mode_prune_invalid(struct drm_device *dev, list_for_each_entry_safe(mode, t, mode_list, head) { if (mode->status != MODE_OK) { list_del(&mode->head); + if (mode->type & DRM_MODE_TYPE_USERDEF) { + drm_warn(dev, "User-defined mode not supported: " + DRM_MODE_FMT "\n", DRM_MODE_ARG(mode)); + } if (verbose) { - drm_mode_debug_printmodeline(mode); - DRM_DEBUG_KMS("Not using %s mode: %s\n", - mode->name, - drm_get_mode_status_name(mode->status)); + drm_dbg_kms(dev, "Rejected mode: " DRM_MODE_FMT " (%s)\n", + DRM_MODE_ARG(mode), drm_get_mode_status_name(mode->status)); } drm_mode_destroy(dev, mode); } @@ -1318,7 +1838,8 @@ EXPORT_SYMBOL(drm_mode_prune_invalid); * Negative if @lh_a is better than @lh_b, zero if they're equivalent, or * positive if @lh_b is better than @lh_a. */ -static int drm_mode_compare(void *priv, struct list_head *lh_a, struct list_head *lh_b) +static int drm_mode_compare(void *priv, const struct list_head *lh_a, + const struct list_head *lh_b) { struct drm_display_mode *a = list_entry(lh_a, struct drm_display_mode, head); struct drm_display_mode *b = list_entry(lh_b, struct drm_display_mode, head); @@ -1332,7 +1853,7 @@ static int drm_mode_compare(void *priv, struct list_head *lh_a, struct list_head if (diff) return diff; - diff = b->vrefresh - a->vrefresh; + diff = drm_mode_vrefresh(b) - drm_mode_vrefresh(a); if (diff) return diff; @@ -1414,6 +1935,405 @@ void drm_connector_list_update(struct drm_connector *connector) } EXPORT_SYMBOL(drm_connector_list_update); +static int drm_mode_parse_cmdline_bpp(const char *str, char **end_ptr, + struct drm_cmdline_mode *mode) +{ + unsigned int bpp; + + if (str[0] != '-') + return -EINVAL; + + str++; + bpp = simple_strtol(str, end_ptr, 10); + if (*end_ptr == str) + return -EINVAL; + + mode->bpp = bpp; + mode->bpp_specified = true; + + return 0; +} + +static int drm_mode_parse_cmdline_refresh(const char *str, char **end_ptr, + struct drm_cmdline_mode *mode) +{ + unsigned int refresh; + + if (str[0] != '@') + return -EINVAL; + + str++; + refresh = simple_strtol(str, end_ptr, 10); + if (*end_ptr == str) + return -EINVAL; + + mode->refresh = refresh; + mode->refresh_specified = true; + + return 0; +} + +static int drm_mode_parse_cmdline_extra(const char *str, int length, + bool freestanding, + const struct drm_connector *connector, + struct drm_cmdline_mode *mode) +{ + int i; + + for (i = 0; i < length; i++) { + switch (str[i]) { + case 'i': + if (freestanding) + return -EINVAL; + + mode->interlace = true; + break; + case 'm': + if (freestanding) + return -EINVAL; + + mode->margins = true; + break; + case 'D': + if (mode->force != DRM_FORCE_UNSPECIFIED) + return -EINVAL; + + if ((connector->connector_type != DRM_MODE_CONNECTOR_DVII) && + (connector->connector_type != DRM_MODE_CONNECTOR_HDMIB)) + mode->force = DRM_FORCE_ON; + else + mode->force = DRM_FORCE_ON_DIGITAL; + break; + case 'd': + if (mode->force != DRM_FORCE_UNSPECIFIED) + return -EINVAL; + + mode->force = DRM_FORCE_OFF; + break; + case 'e': + if (mode->force != DRM_FORCE_UNSPECIFIED) + return -EINVAL; + + mode->force = DRM_FORCE_ON; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static int drm_mode_parse_cmdline_res_mode(const char *str, unsigned int length, + bool extras, + const struct drm_connector *connector, + struct drm_cmdline_mode *mode) +{ + const char *str_start = str; + bool rb = false, cvt = false; + int xres = 0, yres = 0; + int remaining, i; + char *end_ptr; + + xres = simple_strtol(str, &end_ptr, 10); + if (end_ptr == str) + return -EINVAL; + + if (end_ptr[0] != 'x') + return -EINVAL; + end_ptr++; + + str = end_ptr; + yres = simple_strtol(str, &end_ptr, 10); + if (end_ptr == str) + return -EINVAL; + + remaining = length - (end_ptr - str_start); + if (remaining < 0) + return -EINVAL; + + for (i = 0; i < remaining; i++) { + switch (end_ptr[i]) { + case 'M': + cvt = true; + break; + case 'R': + rb = true; + break; + default: + /* + * Try to pass that to our extras parsing + * function to handle the case where the + * extras are directly after the resolution + */ + if (extras) { + int ret = drm_mode_parse_cmdline_extra(end_ptr + i, + 1, + false, + connector, + mode); + if (ret) + return ret; + } else { + return -EINVAL; + } + } + } + + mode->xres = xres; + mode->yres = yres; + mode->cvt = cvt; + mode->rb = rb; + + return 0; +} + +static int drm_mode_parse_cmdline_int(const char *delim, unsigned int *int_ret) +{ + const char *value; + char *endp; + + /* + * delim must point to the '=', otherwise it is a syntax error and + * if delim points to the terminating zero, then delim + 1 will point + * past the end of the string. + */ + if (*delim != '=') + return -EINVAL; + + value = delim + 1; + *int_ret = simple_strtol(value, &endp, 10); + + /* Make sure we have parsed something */ + if (endp == value) + return -EINVAL; + + return 0; +} + +static int drm_mode_parse_panel_orientation(const char *delim, + struct drm_cmdline_mode *mode) +{ + const char *value; + + if (*delim != '=') + return -EINVAL; + + value = delim + 1; + delim = strchr(value, ','); + if (!delim) + delim = value + strlen(value); + + if (!strncmp(value, "normal", delim - value)) + mode->panel_orientation = DRM_MODE_PANEL_ORIENTATION_NORMAL; + else if (!strncmp(value, "upside_down", delim - value)) + mode->panel_orientation = DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP; + else if (!strncmp(value, "left_side_up", delim - value)) + mode->panel_orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP; + else if (!strncmp(value, "right_side_up", delim - value)) + mode->panel_orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP; + else + return -EINVAL; + + return 0; +} + +static int drm_mode_parse_tv_mode(const char *delim, + struct drm_cmdline_mode *mode) +{ + const char *value; + int ret; + + if (*delim != '=') + return -EINVAL; + + value = delim + 1; + delim = strchr(value, ','); + if (!delim) + delim = value + strlen(value); + + ret = drm_get_tv_mode_from_name(value, delim - value); + if (ret < 0) + return ret; + + mode->tv_mode_specified = true; + mode->tv_mode = ret; + + return 0; +} + +static int drm_mode_parse_cmdline_options(const char *str, + bool freestanding, + const struct drm_connector *connector, + struct drm_cmdline_mode *mode) +{ + unsigned int deg, margin, rotation = 0; + const char *delim, *option, *sep; + + option = str; + do { + delim = strchr(option, '='); + if (!delim) { + delim = strchr(option, ','); + + if (!delim) + delim = option + strlen(option); + } + + if (!strncmp(option, "rotate", delim - option)) { + if (drm_mode_parse_cmdline_int(delim, °)) + return -EINVAL; + + switch (deg) { + case 0: + rotation |= DRM_MODE_ROTATE_0; + break; + + case 90: + rotation |= DRM_MODE_ROTATE_90; + break; + + case 180: + rotation |= DRM_MODE_ROTATE_180; + break; + + case 270: + rotation |= DRM_MODE_ROTATE_270; + break; + + default: + return -EINVAL; + } + } else if (!strncmp(option, "reflect_x", delim - option)) { + rotation |= DRM_MODE_REFLECT_X; + } else if (!strncmp(option, "reflect_y", delim - option)) { + rotation |= DRM_MODE_REFLECT_Y; + } else if (!strncmp(option, "margin_right", delim - option)) { + if (drm_mode_parse_cmdline_int(delim, &margin)) + return -EINVAL; + + mode->tv_margins.right = margin; + } else if (!strncmp(option, "margin_left", delim - option)) { + if (drm_mode_parse_cmdline_int(delim, &margin)) + return -EINVAL; + + mode->tv_margins.left = margin; + } else if (!strncmp(option, "margin_top", delim - option)) { + if (drm_mode_parse_cmdline_int(delim, &margin)) + return -EINVAL; + + mode->tv_margins.top = margin; + } else if (!strncmp(option, "margin_bottom", delim - option)) { + if (drm_mode_parse_cmdline_int(delim, &margin)) + return -EINVAL; + + mode->tv_margins.bottom = margin; + } else if (!strncmp(option, "panel_orientation", delim - option)) { + if (drm_mode_parse_panel_orientation(delim, mode)) + return -EINVAL; + } else if (!strncmp(option, "tv_mode", delim - option)) { + if (drm_mode_parse_tv_mode(delim, mode)) + return -EINVAL; + } else { + return -EINVAL; + } + sep = strchr(delim, ','); + option = sep + 1; + } while (sep); + + if (rotation && freestanding) + return -EINVAL; + + if (!(rotation & DRM_MODE_ROTATE_MASK)) + rotation |= DRM_MODE_ROTATE_0; + + /* Make sure there is exactly one rotation defined */ + if (!is_power_of_2(rotation & DRM_MODE_ROTATE_MASK)) + return -EINVAL; + + mode->rotation_reflection = rotation; + + return 0; +} + +struct drm_named_mode { + const char *name; + unsigned int pixel_clock_khz; + unsigned int xres; + unsigned int yres; + unsigned int flags; + unsigned int tv_mode; +}; + +#define NAMED_MODE(_name, _pclk, _x, _y, _flags, _mode) \ + { \ + .name = _name, \ + .pixel_clock_khz = _pclk, \ + .xres = _x, \ + .yres = _y, \ + .flags = _flags, \ + .tv_mode = _mode, \ + } + +static const struct drm_named_mode drm_named_modes[] = { + NAMED_MODE("NTSC", 13500, 720, 480, DRM_MODE_FLAG_INTERLACE, DRM_MODE_TV_MODE_NTSC), + NAMED_MODE("NTSC-J", 13500, 720, 480, DRM_MODE_FLAG_INTERLACE, DRM_MODE_TV_MODE_NTSC_J), + NAMED_MODE("PAL", 13500, 720, 576, DRM_MODE_FLAG_INTERLACE, DRM_MODE_TV_MODE_PAL), + NAMED_MODE("PAL-M", 13500, 720, 480, DRM_MODE_FLAG_INTERLACE, DRM_MODE_TV_MODE_PAL_M), +}; + +static int drm_mode_parse_cmdline_named_mode(const char *name, + unsigned int name_end, + struct drm_cmdline_mode *cmdline_mode) +{ + unsigned int i; + + if (!name_end) + return 0; + + /* If the name starts with a digit, it's not a named mode */ + if (isdigit(name[0])) + return 0; + + /* + * If there's an equal sign in the name, the command-line + * contains only an option and no mode. + */ + if (strnchr(name, name_end, '=')) + return 0; + + /* The connection status extras can be set without a mode. */ + if (name_end == 1 && + (name[0] == 'd' || name[0] == 'D' || name[0] == 'e')) + return 0; + + /* + * We're sure we're a named mode at this point, iterate over the + * list of modes we're aware of. + */ + for (i = 0; i < ARRAY_SIZE(drm_named_modes); i++) { + const struct drm_named_mode *mode = &drm_named_modes[i]; + int ret; + + ret = str_has_prefix(name, mode->name); + if (ret != name_end) + continue; + + strscpy(cmdline_mode->name, mode->name, sizeof(cmdline_mode->name)); + cmdline_mode->pixel_clock = mode->pixel_clock_khz; + cmdline_mode->xres = mode->xres; + cmdline_mode->yres = mode->yres; + cmdline_mode->interlace = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + cmdline_mode->tv_mode = mode->tv_mode; + cmdline_mode->tv_mode_specified = true; + cmdline_mode->specified = true; + + return 1; + } + + return -EINVAL; +} + /** * drm_mode_parse_command_line_for_connector - parse command line modeline for connector * @mode_option: optional per connector mode option @@ -1421,14 +2341,17 @@ EXPORT_SYMBOL(drm_connector_list_update); * @mode: preallocated drm_cmdline_mode structure to fill out * * This parses @mode_option command line modeline for modes and options to - * configure the connector. If @mode_option is NULL the default command line - * modeline in fb_mode_option will be parsed instead. + * configure the connector. * * This uses the same parameters as the fb modedb.c, except for an extra * force-enable, force-enable-digital and force-disable bit at the end:: * * <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m][eDd] * + * Additionals options can be provided following the mode, using a comma to + * separate each option. Valid options can be found in + * Documentation/fb/modedb.rst. + * * The intermediate drm_cmdline_mode structure is required to store additional * options from the command line modline like the force-enable/disable flag. * @@ -1436,155 +2359,175 @@ EXPORT_SYMBOL(drm_connector_list_update); * True if a valid modeline has been parsed, false otherwise. */ bool drm_mode_parse_command_line_for_connector(const char *mode_option, - struct drm_connector *connector, + const struct drm_connector *connector, struct drm_cmdline_mode *mode) { const char *name; - unsigned int namelen; - bool res_specified = false, bpp_specified = false, refresh_specified = false; - unsigned int xres = 0, yres = 0, bpp = 32, refresh = 0; - bool yres_specified = false, cvt = false, rb = false; - bool interlace = false, margins = false, was_digit = false; - int i; - enum drm_connector_force force = DRM_FORCE_UNSPECIFIED; + bool freestanding = false, parse_extras = false; + unsigned int bpp_off = 0, refresh_off = 0, options_off = 0; + unsigned int mode_end = 0; + const char *bpp_ptr = NULL, *refresh_ptr = NULL, *extra_ptr = NULL; + const char *options_ptr = NULL; + char *bpp_end_ptr = NULL, *refresh_end_ptr = NULL; + int len, ret; -#ifdef CONFIG_FB - if (!mode_option) - mode_option = fb_mode_option; -#endif + memset(mode, 0, sizeof(*mode)); + mode->panel_orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN; - if (!mode_option) { - mode->specified = false; + if (!mode_option) return false; - } name = mode_option; - namelen = strlen(name); - for (i = namelen-1; i >= 0; i--) { - switch (name[i]) { - case '@': - if (!refresh_specified && !bpp_specified && - !yres_specified && !cvt && !rb && was_digit) { - refresh = simple_strtol(&name[i+1], NULL, 10); - refresh_specified = true; - was_digit = false; - } else - goto done; - break; - case '-': - if (!bpp_specified && !yres_specified && !cvt && - !rb && was_digit) { - bpp = simple_strtol(&name[i+1], NULL, 10); - bpp_specified = true; - was_digit = false; - } else - goto done; - break; - case 'x': - if (!yres_specified && was_digit) { - yres = simple_strtol(&name[i+1], NULL, 10); - yres_specified = true; - was_digit = false; - } else - goto done; - break; - case '0' ... '9': - was_digit = true; - break; - case 'M': - if (yres_specified || cvt || was_digit) - goto done; - cvt = true; - break; - case 'R': - if (yres_specified || cvt || rb || was_digit) - goto done; - rb = true; - break; - case 'm': - if (cvt || yres_specified || was_digit) - goto done; - margins = true; - break; - case 'i': - if (cvt || yres_specified || was_digit) - goto done; - interlace = true; - break; - case 'e': - if (yres_specified || bpp_specified || refresh_specified || - was_digit || (force != DRM_FORCE_UNSPECIFIED)) - goto done; - force = DRM_FORCE_ON; - break; - case 'D': - if (yres_specified || bpp_specified || refresh_specified || - was_digit || (force != DRM_FORCE_UNSPECIFIED)) - goto done; + /* Locate the start of named options */ + options_ptr = strchr(name, ','); + if (options_ptr) + options_off = options_ptr - name; + else + options_off = strlen(name); + + /* Try to locate the bpp and refresh specifiers, if any */ + bpp_ptr = strnchr(name, options_off, '-'); + while (bpp_ptr && !isdigit(bpp_ptr[1])) + bpp_ptr = strnchr(bpp_ptr + 1, options_off, '-'); + if (bpp_ptr) + bpp_off = bpp_ptr - name; + + refresh_ptr = strnchr(name, options_off, '@'); + if (refresh_ptr) + refresh_off = refresh_ptr - name; + + /* Locate the end of the name / resolution, and parse it */ + if (bpp_ptr) { + mode_end = bpp_off; + } else if (refresh_ptr) { + mode_end = refresh_off; + } else if (options_ptr) { + mode_end = options_off; + parse_extras = true; + } else { + mode_end = strlen(name); + parse_extras = true; + } - if ((connector->connector_type != DRM_MODE_CONNECTOR_DVII) && - (connector->connector_type != DRM_MODE_CONNECTOR_HDMIB)) - force = DRM_FORCE_ON; - else - force = DRM_FORCE_ON_DIGITAL; - break; - case 'd': - if (yres_specified || bpp_specified || refresh_specified || - was_digit || (force != DRM_FORCE_UNSPECIFIED)) - goto done; + if (!mode_end) + return false; - force = DRM_FORCE_OFF; - break; - default: - goto done; - } + ret = drm_mode_parse_cmdline_named_mode(name, mode_end, mode); + if (ret < 0) + return false; + + /* + * Having a mode that starts by a letter (and thus is named) and + * an at-sign (used to specify a refresh rate) is disallowed. + */ + if (ret && refresh_ptr) + return false; + + /* No named mode? Check for a normal mode argument, e.g. 1024x768 */ + if (!mode->specified && isdigit(name[0])) { + ret = drm_mode_parse_cmdline_res_mode(name, mode_end, + parse_extras, + connector, + mode); + if (ret) + return false; + + mode->specified = true; } - if (i < 0 && yres_specified) { - char *ch; - xres = simple_strtol(name, &ch, 10); - if ((ch != NULL) && (*ch == 'x')) - res_specified = true; + /* No mode? Check for freestanding extras and/or options */ + if (!mode->specified) { + unsigned int len = strlen(mode_option); + + if (bpp_ptr || refresh_ptr) + return false; /* syntax error */ + + if (len == 1 || (len >= 2 && mode_option[1] == ',')) + extra_ptr = mode_option; else - i = ch - name; - } else if (!yres_specified && was_digit) { - /* catch mode that begins with digits but has no 'x' */ - i = 0; - } -done: - if (i >= 0) { - pr_warn("[drm] parse error at position %i in video mode '%s'\n", - i, name); - mode->specified = false; - return false; + options_ptr = mode_option - 1; + + freestanding = true; } - if (res_specified) { - mode->specified = true; - mode->xres = xres; - mode->yres = yres; + if (bpp_ptr) { + ret = drm_mode_parse_cmdline_bpp(bpp_ptr, &bpp_end_ptr, mode); + if (ret) + return false; + + mode->bpp_specified = true; } - if (refresh_specified) { + if (refresh_ptr) { + ret = drm_mode_parse_cmdline_refresh(refresh_ptr, + &refresh_end_ptr, mode); + if (ret) + return false; + mode->refresh_specified = true; - mode->refresh = refresh; } - if (bpp_specified) { - mode->bpp_specified = true; - mode->bpp = bpp; + /* + * Locate the end of the bpp / refresh, and parse the extras + * if relevant + */ + if (bpp_ptr && refresh_ptr) + extra_ptr = max(bpp_end_ptr, refresh_end_ptr); + else if (bpp_ptr) + extra_ptr = bpp_end_ptr; + else if (refresh_ptr) + extra_ptr = refresh_end_ptr; + + if (extra_ptr) { + if (options_ptr) + len = options_ptr - extra_ptr; + else + len = strlen(extra_ptr); + + ret = drm_mode_parse_cmdline_extra(extra_ptr, len, freestanding, + connector, mode); + if (ret) + return false; + } + + if (options_ptr) { + ret = drm_mode_parse_cmdline_options(options_ptr + 1, + freestanding, + connector, mode); + if (ret) + return false; } - mode->rb = rb; - mode->cvt = cvt; - mode->interlace = interlace; - mode->margins = margins; - mode->force = force; return true; } EXPORT_SYMBOL(drm_mode_parse_command_line_for_connector); +static struct drm_display_mode *drm_named_mode(struct drm_device *dev, + struct drm_cmdline_mode *cmd) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(drm_named_modes); i++) { + const struct drm_named_mode *named_mode = &drm_named_modes[i]; + + if (strcmp(cmd->name, named_mode->name)) + continue; + + if (!cmd->tv_mode_specified) + continue; + + return drm_analog_tv_mode(dev, + named_mode->tv_mode, + named_mode->pixel_clock_khz * 1000, + named_mode->xres, + named_mode->yres, + named_mode->flags & DRM_MODE_FLAG_INTERLACE); + } + + return NULL; +} + /** * drm_mode_create_from_cmdline_mode - convert a command line modeline into a DRM display mode * @dev: DRM device to create the new mode for @@ -1599,7 +2542,12 @@ drm_mode_create_from_cmdline_mode(struct drm_device *dev, { struct drm_display_mode *mode; - if (cmd->cvt) + if (cmd->xres == 0 || cmd->yres == 0) + return NULL; + + if (strlen(cmd->name)) + mode = drm_named_mode(dev, cmd); + else if (cmd->cvt) mode = drm_cvt_mode(dev, cmd->xres, cmd->yres, cmd->refresh_specified ? cmd->refresh : 60, @@ -1624,7 +2572,7 @@ drm_mode_create_from_cmdline_mode(struct drm_device *dev, EXPORT_SYMBOL(drm_mode_create_from_cmdline_mode); /** - * drm_crtc_convert_to_umode - convert a drm_display_mode into a modeinfo + * drm_mode_convert_to_umode - convert a drm_display_mode into a modeinfo * @out: drm_mode_modeinfo struct to return to the user * @in: drm_display_mode to use * @@ -1634,13 +2582,6 @@ EXPORT_SYMBOL(drm_mode_create_from_cmdline_mode); void drm_mode_convert_to_umode(struct drm_mode_modeinfo *out, const struct drm_display_mode *in) { - WARN(in->hdisplay > USHRT_MAX || in->hsync_start > USHRT_MAX || - in->hsync_end > USHRT_MAX || in->htotal > USHRT_MAX || - in->hskew > USHRT_MAX || in->vdisplay > USHRT_MAX || - in->vsync_start > USHRT_MAX || in->vsync_end > USHRT_MAX || - in->vtotal > USHRT_MAX || in->vscan > USHRT_MAX, - "timing values too large for mode info\n"); - out->clock = in->clock; out->hdisplay = in->hdisplay; out->hsync_start = in->hsync_start; @@ -1652,7 +2593,7 @@ void drm_mode_convert_to_umode(struct drm_mode_modeinfo *out, out->vsync_end = in->vsync_end; out->vtotal = in->vtotal; out->vscan = in->vscan; - out->vrefresh = in->vrefresh; + out->vrefresh = drm_mode_vrefresh(in); out->flags = in->flags; out->type = in->type; @@ -1669,18 +2610,20 @@ void drm_mode_convert_to_umode(struct drm_mode_modeinfo *out, case HDMI_PICTURE_ASPECT_256_135: out->flags |= DRM_MODE_FLAG_PIC_AR_256_135; break; - case HDMI_PICTURE_ASPECT_RESERVED: default: + WARN(1, "Invalid aspect ratio (0%x) on mode\n", + in->picture_aspect_ratio); + fallthrough; + case HDMI_PICTURE_ASPECT_NONE: out->flags |= DRM_MODE_FLAG_PIC_AR_NONE; break; } - strncpy(out->name, in->name, DRM_DISPLAY_MODE_LEN); - out->name[DRM_DISPLAY_MODE_LEN-1] = 0; + strscpy_pad(out->name, in->name, sizeof(out->name)); } /** - * drm_crtc_convert_umode - convert a modeinfo into a drm_display_mode + * drm_mode_convert_umode - convert a modeinfo into a drm_display_mode * @dev: drm device * @out: drm_display_mode to return to the user * @in: drm_mode_modeinfo to use @@ -1709,17 +2652,15 @@ int drm_mode_convert_umode(struct drm_device *dev, out->vsync_end = in->vsync_end; out->vtotal = in->vtotal; out->vscan = in->vscan; - out->vrefresh = in->vrefresh; out->flags = in->flags; /* * Old xf86-video-vmware (possibly others too) used to - * leave 'type' unititialized. Just ignore any bits we + * leave 'type' uninitialized. Just ignore any bits we * don't like. It's a just hint after all, and more * useful for the kernel->userspace direction anyway. */ out->type = in->type & DRM_MODE_TYPE_ALL; - strncpy(out->name, in->name, DRM_DISPLAY_MODE_LEN); - out->name[DRM_DISPLAY_MODE_LEN-1] = 0; + strscpy_pad(out->name, in->name, sizeof(out->name)); /* Clearing picture aspect ratio bits from out flags, * as the aspect-ratio information is not stored in @@ -1729,20 +2670,22 @@ int drm_mode_convert_umode(struct drm_device *dev, switch (in->flags & DRM_MODE_FLAG_PIC_AR_MASK) { case DRM_MODE_FLAG_PIC_AR_4_3: - out->picture_aspect_ratio |= HDMI_PICTURE_ASPECT_4_3; + out->picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3; break; case DRM_MODE_FLAG_PIC_AR_16_9: - out->picture_aspect_ratio |= HDMI_PICTURE_ASPECT_16_9; + out->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9; break; case DRM_MODE_FLAG_PIC_AR_64_27: - out->picture_aspect_ratio |= HDMI_PICTURE_ASPECT_64_27; + out->picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27; break; case DRM_MODE_FLAG_PIC_AR_256_135: - out->picture_aspect_ratio |= HDMI_PICTURE_ASPECT_256_135; + out->picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135; break; - default: + case DRM_MODE_FLAG_PIC_AR_NONE: out->picture_aspect_ratio = HDMI_PICTURE_ASPECT_NONE; break; + default: + return -EINVAL; } out->status = drm_mode_validate_driver(dev, out); @@ -1811,3 +2754,25 @@ bool drm_mode_is_420(const struct drm_display_info *display, drm_mode_is_420_also(display, mode); } EXPORT_SYMBOL(drm_mode_is_420); + +/** + * drm_set_preferred_mode - Sets the preferred mode of a connector + * @connector: connector whose mode list should be processed + * @hpref: horizontal resolution of preferred mode + * @vpref: vertical resolution of preferred mode + * + * Marks a mode as preferred if it matches the resolution specified by @hpref + * and @vpref. + */ +void drm_set_preferred_mode(struct drm_connector *connector, + int hpref, int vpref) +{ + struct drm_display_mode *mode; + + list_for_each_entry(mode, &connector->probed_modes, head) { + if (mode->hdisplay == hpref && + mode->vdisplay == vpref) + mode->type |= DRM_MODE_TYPE_PREFERRED; + } +} +EXPORT_SYMBOL(drm_set_preferred_mode); |
