diff options
Diffstat (limited to 'drivers/gpu/drm/drm_modes.c')
| -rw-r--r-- | drivers/gpu/drm/drm_modes.c | 800 |
1 files changed, 752 insertions, 48 deletions
diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index ae53ea624c73..e72f855fc495 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -31,15 +31,19 @@ */ #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/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> @@ -113,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 @@ -127,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. @@ -720,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 */ @@ -752,19 +1282,15 @@ EXPORT_SYMBOL(drm_mode_set_name); * @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) { - unsigned int num, den; + unsigned int num = 1, den = 1; if (mode->htotal == 0 || mode->vtotal == 0) return 0; - num = mode->clock; - den = mode->htotal * mode->vtotal; - if (mode->flags & DRM_MODE_FLAG_INTERLACE) num *= 2; if (mode->flags & DRM_MODE_FLAG_DBLSCAN) @@ -772,6 +1298,12 @@ int drm_mode_vrefresh(const struct drm_display_mode *mode) if (mode->vscan > 1) den *= mode->vscan; + if (check_mul_overflow(mode->clock, num, &num)) + return 0; + + 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); @@ -788,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; @@ -880,7 +1414,7 @@ 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) @@ -893,6 +1427,23 @@ void drm_mode_copy(struct drm_display_mode *dst, const struct drm_display_mode * 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 @@ -1260,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); } @@ -1542,7 +2095,7 @@ static int drm_mode_parse_cmdline_int(const char *delim, unsigned int *int_ret) /* * delim must point to the '=', otherwise it is a syntax error and - * if delim points to the terminating zero, then delim + 1 wil point + * if delim points to the terminating zero, then delim + 1 will point * past the end of the string. */ if (*delim != '=') @@ -1585,6 +2138,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, @@ -1654,6 +2231,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; } @@ -1676,11 +2256,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 @@ -1688,8 +2341,7 @@ static const char * const drm_named_modes_whitelist[] = { * @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:: @@ -1717,7 +2369,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; @@ -1727,20 +2379,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; @@ -1754,18 +2410,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])) { @@ -1846,6 +2503,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 @@ -1863,7 +2545,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, @@ -1935,8 +2619,7 @@ void drm_mode_convert_to_umode(struct drm_mode_modeinfo *out, 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)); } /** @@ -1972,13 +2655,12 @@ int drm_mode_convert_umode(struct drm_device *dev, 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 @@ -2072,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); |
