From 1bb6a9c94ea087ffb94e19fb2d91d2ef885a338d Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 10 Apr 2012 11:01:13 +0200 Subject: [PATCH] Allow cloning of laptop displays References: bnc#753172 On many laptops with the current intel driver don't support the clone mode when connected with an external monitor. There are two reasons: 1. the laptop panel has no matching resolutions for external monitors 2. the external monitors have no matching resolution for the laptop panel native mode The former case is found, typically, on a laptop with 16:9 panel. Since we add only the old VESA-default modes such as 1024x768 with 4:3 aspect ratio, a 16:9 resolution like 1280x720 isn't be supported unless the user adds it manually. The laptop panel is always with a panel fitter, thus it should be able to support any resolutions that are lower than the native resolution. So, it's simply a missing setup. The latter case corresponds to some monitors that don't provide the resolution like 1366x768 or 1600x900 via EDID. Although they do work with such a resolution when set manually once, the current driver doesn't support it because EDID doesn't contain it. It seems that AMD fglrx and Windows add such a mode forcibly to allow the cloning. This patch fixes these issues by - Adding de facto standard resolutions to laptop panel, and - Adding the native panel mode to others For the second action, the panel resolution that doesn't match well with the monitor's aspect ratio is ignored because it doesn't make much sense for the original purpose, cloning the output. That is, when a monitor is 4:3, 16:9 laptop panel mode won't be added. Signed-off-by: Takashi Iwai --- src/intel.h | 2 src/intel_display.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) --- a/src/intel.h +++ b/src/intel.h @@ -445,6 +445,8 @@ typedef struct intel_screen_private { struct udev_monitor *uevent_monitor; InputHandlerProc uevent_handler; #endif + + DisplayModePtr panel_fixed_modes; } intel_screen_private; enum { --- a/src/intel_display.c +++ b/src/intel_display.c @@ -793,6 +793,193 @@ intel_output_attach_edid(xf86OutputPtr o drmModeFreePropertyBlob(edid_blob); } +static Bool +mode_is_present(DisplayModePtr m, int width, int height) +{ + for (; m; m = m->next) { + if (m->status == MODE_OK && + m->HDisplay == width && + m->VDisplay == height) + return TRUE; + } + return FALSE; +} + +static DisplayModePtr +get_native_mode(DisplayModePtr m) +{ + for (; m; m = m->next) { + if (m->type & M_T_PREFERRED) { + if (m->status == MODE_OK) + return m; + break; + } + } + return NULL; +} + +#define MODELINE(_clk, _hdisp, _hstart, _hend, _htotal, _vdisp, _vstart, _vend, _vtotal, _hsync, _vsync) { \ + .clock = (int)(_clk * 1000), \ + .hdisplay = _hdisp, .hsync_start = _hstart, .hsync_end = _hend, .htotal = _htotal, \ + .vdisplay = _vdisp, .vsync_start = _vstart, .vsync_end = _vend, .vtotal = _vtotal, \ + .flags = (_hsync < 0 ? DRM_MODE_FLAG_NHSYNC : (_hsync > 0 ? DRM_MODE_FLAG_PHSYNC : 0)) | \ + (_vsync < 0 ? DRM_MODE_FLAG_NVSYNC : (_vsync > 0 ? DRM_MODE_FLAG_PVSYNC : 0)), \ + } + +static drmModeModeInfo standard_modes[] = { +MODELINE(138.50, 1920, 1968, 2000, 2080, 1080, 1083, 1088, 1111, 1, -1), +MODELINE(119.00, 1680, 1728, 1760, 1840, 1050, 1053, 1059, 1080, 1, -1), +MODELINE(88.75, 1440, 1488, 1520, 1600, 900, 903, 909, 926, 1, -1), +MODELINE(63.75, 1280, 1328, 1360, 1440, 720, 723, 728, 741, 1, -1), +}; + +static drmModeModeInfo fixed_modes[] = { +MODELINE(97.50, 1600, 1648, 1680, 1760, 900, 903, 908, 926, 1, -1), +MODELINE(72.25, 1366, 1416, 1448, 1528, 768, 771, 781, 790, 1, -1), +MODELINE(72.00, 1360, 1408, 1440, 1520, 768, 771, 781, 790, 1, -1), +MODELINE(71.00, 1280, 1328, 1360, 1440, 800, 803, 809, 823, 1, -1), +}; + +static drmModeModeInfoPtr +check_matching_std_kmode(DisplayModePtr base) +{ + int i; + for (i = 0; i < ARRAY_SIZE(standard_modes); i++) { + if (standard_modes[i].hdisplay == base->HDisplay && + standard_modes[i].vdisplay == base->VDisplay) + return &standard_modes[i]; + } + for (i = 0; i < ARRAY_SIZE(fixed_modes); i++) { + if (fixed_modes[i].hdisplay == base->HDisplay && + fixed_modes[i].vdisplay == base->VDisplay) + return &fixed_modes[i]; + } + return NULL; +} + +static DisplayModePtr +get_faked_mode(xf86OutputPtr output, drmModeModeInfoPtr kmode, + DisplayModePtr base) +{ + ScrnInfoPtr scrn = output->scrn; + DisplayModePtr m; + + if (!kmode) + kmode = check_matching_std_kmode(base); + + if (kmode) { + m = xnfalloc(sizeof(*m)); + mode_from_kmode(scrn, kmode, m); + } else { + m = xf86DuplicateMode(base); + } + m->type &= ~M_T_PREFERRED; + xf86SetModeDefaultName(m); + return m; +} + +static Bool +is_display_size_ok(DisplayModePtr modes, DisplayModePtr native, + int width, int height, Bool check_aspect) +{ + /* the resolution already exists? */ + if (mode_is_present(modes, width, height)) + return FALSE; + /* don't add higher resolutions */ + if (width > native->HDisplay || height > native->VDisplay) + return FALSE; + /* don't add modes with higher aspect ratio than native */ + /* (add 20% tolerance for supporting 16:10 format) */ + if (check_aspect && + width * native->VDisplay > height * native->HDisplay * 12 / 10) + return FALSE; + return TRUE; +} + +static int +intel_output_get_connector_type(xf86OutputPtr output) +{ + struct intel_output *intel_output = output->driver_private; + drmModeConnectorPtr koutput = intel_output->mode_output; + return koutput->connector_type; +} + +static Bool can_add_extra_modes(xf86OutputPtr output) +{ + switch (intel_output_get_connector_type(output)) { + case DRM_MODE_CONNECTOR_VGA: + case DRM_MODE_CONNECTOR_DVII: + case DRM_MODE_CONNECTOR_DVID: + case DRM_MODE_CONNECTOR_DVIA: + case DRM_MODE_CONNECTOR_DisplayPort: + case DRM_MODE_CONNECTOR_HDMIA: + case DRM_MODE_CONNECTOR_HDMIB: + return TRUE; + } + return FALSE; +} + +/* Add de facto standard modes for laptop displays */ +static DisplayModePtr +intel_add_standard_modes(xf86OutputPtr output, DisplayModePtr modes) +{ + DisplayModePtr native, m; + int i; + + native = get_native_mode(modes); + if (!native) + return modes; + + for (i = 0; i < ARRAY_SIZE(standard_modes); i++) { + drmModeModeInfoPtr p = &standard_modes[i]; + if (!is_display_size_ok(modes, native, + p->hdisplay, p->vdisplay, FALSE)) + continue; + m = get_faked_mode(output, p, native); + modes = xf86ModesAdd(modes, m); + } + return modes; +} + +/* Add the modes of laptop fixed panels if possible */ +static DisplayModePtr +intel_add_panel_fixed_modes(xf86OutputPtr output, DisplayModePtr modes) +{ + intel_screen_private *intel = intel_get_screen_private(output->scrn); + DisplayModePtr native, m; + + native = get_native_mode(modes); + if (!native) + return modes; + + if (is_panel(intel_output_get_connector_type(output))) { + /* remember the fixed mode if not done yet */ + if (!mode_is_present(intel->panel_fixed_modes, + native->HDisplay, native->VDisplay)) { + m = xf86DuplicateMode(native); + intel->panel_fixed_modes = + xf86ModesAdd(intel->panel_fixed_modes, m); + } + } else if (can_add_extra_modes(output)) { + /* add the modes of the fixed panels */ + for (m = intel->panel_fixed_modes; m; m = m->next) { + if (!is_display_size_ok(modes, native, + m->HDisplay, m->VDisplay, TRUE)) + continue; + modes = xf86ModesAdd(modes, get_faked_mode(output, NULL, m)); + } + } + return modes; +} + +static void +intel_free_panel_fixed_modes(intel_screen_private *intel) +{ + while (intel->panel_fixed_modes) + xf86DeleteMode(&intel->panel_fixed_modes, + intel->panel_fixed_modes); +} + static DisplayModePtr intel_output_panel_edid(xf86OutputPtr output, DisplayModePtr modes) { @@ -836,6 +1023,8 @@ intel_output_panel_edid(xf86OutputPtr ou modes = xf86ModesAdd(modes, m); } + modes = intel_add_standard_modes(output, modes); + return modes; } @@ -885,6 +1074,8 @@ intel_output_get_modes(xf86OutputPtr out Modes = intel_output_panel_edid(output, Modes); } + Modes = intel_add_panel_fixed_modes(output, Modes); + return Modes; } @@ -1687,6 +1878,8 @@ intel_mode_fini(intel_screen_private *in free(mode); intel->modes = NULL; + + intel_free_panel_fixed_modes(intel); } /* for the mode overlay */