diff options
Diffstat (limited to 'drivers/gpu/drm/drm_modes.c')
| -rw-r--r-- | drivers/gpu/drm/drm_modes.c | 659 | 
1 files changed, 634 insertions, 25 deletions
diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index 304004fb80aa..40d482a01178 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -31,10 +31,11 @@   */  #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 <linux/fb.h> +#include <linux/of.h>  #include <video/of_display_timing.h>  #include <video/of_videomode.h> @@ -116,6 +117,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 @@ -1659,6 +2136,30 @@ static int drm_mode_parse_panel_orientation(const char *delim,  	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, @@ -1728,6 +2229,9 @@ static int drm_mode_parse_cmdline_options(const char *str,  		} 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;  		} @@ -1750,11 +2254,84 @@ static int drm_mode_parse_cmdline_options(const char *str,  	return 0;  } -static const char * const drm_named_modes_whitelist[] = { -	"NTSC", -	"PAL", +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 @@ -1791,7 +2368,7 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option,  	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 i, len, ret; +	int len, ret;  	memset(mode, 0, sizeof(*mode));  	mode->panel_orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN; @@ -1801,20 +2378,24 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option,  	name = mode_option; +	/* 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 = strchr(name, '-'); +	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 = strchr(name, '@'); +	refresh_ptr = strnchr(name, options_off, '@');  	if (refresh_ptr)  		refresh_off = refresh_ptr - name; -	/* Locate the start of named options */ -	options_ptr = strchr(name, ','); -	if (options_ptr) -		options_off = options_ptr - name; -  	/* Locate the end of the name / resolution, and parse it */  	if (bpp_ptr) {  		mode_end = bpp_off; @@ -1828,18 +2409,19 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option,  		parse_extras = true;  	} -	/* First check for a named mode */ -	for (i = 0; i < ARRAY_SIZE(drm_named_modes_whitelist); i++) { -		ret = str_has_prefix(name, drm_named_modes_whitelist[i]); -		if (ret == mode_end) { -			if (refresh_ptr) -				return false; /* named + refresh is invalid */ +	if (!mode_end) +		return false; -			strcpy(mode->name, drm_named_modes_whitelist[i]); -			mode->specified = true; -			break; -		} -	} +	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])) { @@ -1920,6 +2502,31 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option,  }  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 @@ -1937,7 +2544,9 @@ drm_mode_create_from_cmdline_mode(struct drm_device *dev,  	if (cmd->xres == 0 || cmd->yres == 0)  		return NULL; -	if (cmd->cvt) +	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,  |