diff --git a/man/intel.man b/man/intel.man index e5736e5..473897a 100644 --- a/man/intel.man +++ b/man/intel.man @@ -225,6 +225,24 @@ This method attempts to use the native registers where possible, resorting to th .TP 4 On some system, the kernel may provide a backlight control driver. In that case, using the kernel interfaces is preferable, as the same driver may respond to hotkey events or external APIs. +.PP +.B PANEL_FITTING +- control LCD panel fitting +.TP 2 +By default, the driver will attempt to upscale resolutions smaller than the LCD's native size while preserving the aspect ratio. Other modes are available however: +.PP +.B CENTER +.TP 4 +Simply center the image on-screen, without scaling. +.PP +.B FULL_ASPECT +.TP 4 +The default mode. Try to upscale the image to the screen size, while preserving aspect ratio. May result in letterboxing or pillar-boxing with some resolutions. +.PP +.B FULL +.TP 4 +Upscale the image to the native screen size without regard to aspect ratio. In this mode, the full screen image may appear distorted in some resolutions. + .SS "TV" Integrated TV output. Available properties include: diff --git a/src/i810_reg.h b/src/i810_reg.h index d5b6805..d799e77 100644 --- a/src/i810_reg.h +++ b/src/i810_reg.h @@ -871,8 +871,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define PFIT_CONTROL 0x61230 # define PFIT_ENABLE (1 << 31) -# define PFIT_PIPE_MASK (3 << 29) -# define PFIT_PIPE_SHIFT 29 +/* Pre-965 */ # define VERT_INTERP_DISABLE (0 << 10) # define VERT_INTERP_BILINEAR (1 << 10) # define VERT_INTERP_MASK (3 << 10) @@ -882,12 +881,30 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # define HORIZ_INTERP_MASK (3 << 6) # define HORIZ_AUTO_SCALE (1 << 5) # define PANEL_8TO6_DITHER_ENABLE (1 << 3) +/* 965+ */ +# define PFIT_PIPE_MASK (3 << 29) +# define PFIT_PIPE_SHIFT 29 +# define PFIT_SCALING_MODE_MASK (7 << 26) +# define PFIT_SCALING_AUTO (0 << 26) +# define PFIT_SCALING_PROGRAMMED (1 << 26) +# define PFIT_SCALING_PILLAR (2 << 26) +# define PFIT_SCALING_LETTER (3 << 26) +# define PFIT_FILTER_SELECT_MASK (3 << 24) +# define PFIT_FILTER_FUZZY (0 << 24) +# define PFIT_FILTER_CRISP (1 << 24) +# define PFIT_FILTER_MEDIAN (2 << 24) #define PFIT_PGM_RATIOS 0x61234 +/* Pre-965 */ +# define PFIT_VERT_SCALE_SHIFT 20 # define PFIT_VERT_SCALE_MASK 0xfff00000 +# define PFIT_HORIZ_SCALE_SHIFT 4 # define PFIT_HORIZ_SCALE_MASK 0x0000fff0 - -#define PFIT_AUTO_RATIOS 0x61238 +/* 965+ */ +# define PFIT_VERT_SCALE_SHIFT_965 16 +# define PFIT_VERT_SCALE_MASK_965 0x1fff0000 +# define PFIT_HORIZ_SCALE_SHIFT_965 0 +# define PFIT_HORIZ_SCALE_MASK_965 0x00001fff #define DPLL_A 0x06014 #define DPLL_B 0x06018 diff --git a/src/i830_lvds.c b/src/i830_lvds.c index a75d7e6..daf71da 100644 --- a/src/i830_lvds.c +++ b/src/i830_lvds.c @@ -44,12 +44,25 @@ #include "i830_display.h" #include "X11/Xatom.h" +/* + * Three panel fitting modes: + * CENTER - center image on screen, don't scale + * FULL_ASPECT - scale image to fit screen, but preserve aspect ratio + * FULL - scale image to fit screen without regard to aspect ratio + */ +enum pfit_mode { + CENTER = 0, + FULL_ASPECT, + FULL, +}; + struct i830_lvds_priv { /* The BIOS's fixed timings for the LVDS */ DisplayModePtr panel_fixed_mode; /* The panel needs dithering enabled */ Bool panel_wants_dither; + Bool need_border; /* restore backlight to this value */ int backlight_duty_cycle; @@ -57,6 +70,9 @@ struct i830_lvds_priv { void (*set_backlight)(xf86OutputPtr output, int level); int (*get_backlight)(xf86OutputPtr output); int backlight_max; + enum pfit_mode fitting_mode; + uint32_t pfit_control; + uint32_t pfit_pgm_ratios; }; #define BACKLIGHT_CLASS "/sys/class/backlight" @@ -481,7 +497,13 @@ i830_lvds_mode_fixup(xf86OutputPtr output, DisplayModePtr mode, ScrnInfoPtr pScrn = output->scrn; xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(pScrn); I830CrtcPrivatePtr intel_crtc = output->crtc->driver_private; + I830Ptr pI830 = I830PTR(pScrn); + uint32_t pfit_control = 0, pfit_pgm_ratios = 0; + float panel_ratio, desired_ratio, vert_scale, horiz_scale; + float horiz_ratio, vert_ratio; + int left_border = 0, right_border = 0, top_border = 0, bottom_border = 0; int i; + Bool border = 0; for (i = 0; i < xf86_config->num_output; i++) { xf86OutputPtr other_output = xf86_config->output[i]; @@ -500,24 +522,207 @@ i830_lvds_mode_fixup(xf86OutputPtr output, DisplayModePtr mode, return FALSE; } + /* If we don't have a panel mode there's not much we can do */ + if (dev_priv->panel_fixed_mode == NULL) + return TRUE; + /* If we have timings from the BIOS for the panel, put them in * to the adjusted mode. The CRTC will be set up for this mode, * with the panel scaling set up to source from the H/VDisplay * of the original mode. */ - if (dev_priv->panel_fixed_mode != NULL) { - adjusted_mode->HDisplay = dev_priv->panel_fixed_mode->HDisplay; - adjusted_mode->HSyncStart = dev_priv->panel_fixed_mode->HSyncStart; - adjusted_mode->HSyncEnd = dev_priv->panel_fixed_mode->HSyncEnd; - adjusted_mode->HTotal = dev_priv->panel_fixed_mode->HTotal; - adjusted_mode->VDisplay = dev_priv->panel_fixed_mode->VDisplay; - adjusted_mode->VSyncStart = dev_priv->panel_fixed_mode->VSyncStart; - adjusted_mode->VSyncEnd = dev_priv->panel_fixed_mode->VSyncEnd; - adjusted_mode->VTotal = dev_priv->panel_fixed_mode->VTotal; - adjusted_mode->Clock = dev_priv->panel_fixed_mode->Clock; - xf86SetModeCrtc(adjusted_mode, INTERLACE_HALVE_V); + adjusted_mode->HDisplay = dev_priv->panel_fixed_mode->HDisplay; + adjusted_mode->HSyncStart = dev_priv->panel_fixed_mode->HSyncStart; + adjusted_mode->HSyncEnd = dev_priv->panel_fixed_mode->HSyncEnd; + adjusted_mode->HTotal = dev_priv->panel_fixed_mode->HTotal; + adjusted_mode->VDisplay = dev_priv->panel_fixed_mode->VDisplay; + adjusted_mode->VSyncStart = dev_priv->panel_fixed_mode->VSyncStart; + adjusted_mode->VSyncEnd = dev_priv->panel_fixed_mode->VSyncEnd; + adjusted_mode->VTotal = dev_priv->panel_fixed_mode->VTotal; + adjusted_mode->Clock = dev_priv->panel_fixed_mode->Clock; + xf86SetModeCrtc(adjusted_mode, INTERLACE_HALVE_V); + + /* Native modes don't need fitting */ + if (adjusted_mode->HDisplay == mode->HDisplay && + adjusted_mode->VDisplay == mode->VDisplay) { + pfit_control = 0; + pfit_pgm_ratios = 0; + border = 0; + goto out; + } + + /* Basic panel fitting options */ + if (!IS_I965G(pI830)) { + if (dev_priv->panel_wants_dither) + pfit_control |= PANEL_8TO6_DITHER_ENABLE; + } else { + pfit_control |= (intel_crtc->pipe << PFIT_PIPE_SHIFT) | + PFIT_FILTER_FUZZY; } + /* + * Deal with panel fitting options. Figure out how to stretch the image + * based on its aspect ratio & the current panel fitting mode. + */ + panel_ratio = (float)adjusted_mode->HDisplay / + (float)adjusted_mode->VDisplay; + desired_ratio = (float)mode->HDisplay / + (float)mode->VDisplay; + + /* + * Enable automatic panel scaling for non-native modes so that they fill + * the screen. Should be enabled before the pipe is enabled, according to + * register description and PRM. + */ + /* Change the value here to see the borders for debugging */ + OUTREG(BCLRPAT_A, 0); + OUTREG(BCLRPAT_B, 0); + switch (dev_priv->fitting_mode) { + case CENTER: + /* + * For centered modes, we have to calculate border widths & heights and + * modify the values programmed into the CRTC. Also need to make sure + * LVDS borders are enabled (see i830_display.c). + */ + left_border = + (dev_priv->panel_fixed_mode->HDisplay - mode->HDisplay) / 2; + right_border = left_border; + if (mode->HDisplay & 1) + right_border++; + top_border = + (dev_priv->panel_fixed_mode->VDisplay - mode->VDisplay) / 2; + bottom_border = top_border; + if (mode->VDisplay & 1) + bottom_border++; + + /* Set active & border values */ + adjusted_mode->CrtcHDisplay = mode->HDisplay; + adjusted_mode->CrtcHBlankStart = mode->HDisplay + right_border - 1; + adjusted_mode->CrtcHBlankEnd = adjusted_mode->CrtcHTotal - + left_border - 1; + adjusted_mode->CrtcHSyncStart = adjusted_mode->CrtcHBlankStart; + adjusted_mode->CrtcHSyncEnd = adjusted_mode->CrtcHBlankEnd; + adjusted_mode->CrtcVDisplay = mode->VDisplay; + adjusted_mode->CrtcVBlankStart = mode->VDisplay + bottom_border - 1; + adjusted_mode->CrtcVBlankEnd = adjusted_mode->CrtcVTotal - + top_border - 1; + adjusted_mode->CrtcVSyncStart = adjusted_mode->CrtcVBlankStart; + adjusted_mode->CrtcVSyncEnd = adjusted_mode->CrtcVBlankEnd; + border = 1; + break; + case FULL_ASPECT: + /* Scale but preserve aspect ratio */ + pfit_control |= PFIT_ENABLE; + if (IS_I965G(pI830)) { + /* + * 965+ is easy, it does everything in hw + */ + if (panel_ratio > desired_ratio) + pfit_control |= PFIT_SCALING_PILLAR; + else if (panel_ratio < desired_ratio) + pfit_control |= PFIT_SCALING_LETTER; + else + pfit_control |= PFIT_SCALING_AUTO; + } else { + /* + * For earlier chips we have to calculate the scaling ratio + * by hand and program it into the PFIT_PGM_RATIOS reg. + */ + uint32_t horiz_bits, vert_bits, bits = 12; + + horiz_ratio = ((float)mode->HDisplay) / + ((float)adjusted_mode->HDisplay); + vert_ratio = ((float)mode->VDisplay) / + ((float)adjusted_mode->VDisplay); + + horiz_scale = ((float)adjusted_mode->HDisplay) / + ((float)mode->HDisplay); + vert_scale = ((float)adjusted_mode->VDisplay) / + ((float)mode->VDisplay); + + /* Retain aspect ratio */ + if (panel_ratio > desired_ratio) { /* Pillar */ + unsigned long scaled_width = (float)mode->HDisplay * vert_scale; + + horiz_ratio = vert_ratio; + pfit_control |= VERT_AUTO_SCALE | VERT_INTERP_BILINEAR | + HORIZ_INTERP_BILINEAR; + + /* Pillar will have left/right borders */ + left_border = (dev_priv->panel_fixed_mode->HDisplay - + scaled_width) / 2; + right_border = left_border; + if (mode->HDisplay & 1) /* odd resolutions */ + right_border++; + + adjusted_mode->CrtcHDisplay = scaled_width; + adjusted_mode->CrtcHBlankStart = scaled_width + + right_border - 1; + adjusted_mode->CrtcHBlankEnd = adjusted_mode->CrtcHTotal - + left_border - 1; + adjusted_mode->CrtcHSyncStart = adjusted_mode->CrtcHBlankStart; + adjusted_mode->CrtcHSyncEnd = adjusted_mode->CrtcHBlankEnd; + border = 1; + } else if (panel_ratio < desired_ratio) { /* Letter */ + unsigned long scaled_height = (float)mode->VDisplay * + horiz_scale; + + vert_ratio = horiz_ratio; + pfit_control |= HORIZ_AUTO_SCALE | VERT_INTERP_BILINEAR | + HORIZ_INTERP_BILINEAR; + + /* Letterbox will have top/bottom borders */ + top_border = (dev_priv->panel_fixed_mode->VDisplay - + mode->VDisplay) / 2; + bottom_border = top_border; + if (mode->VDisplay & 1) + bottom_border++; + + adjusted_mode->CrtcVDisplay = scaled_height; + adjusted_mode->CrtcVBlankStart = scaled_height + + bottom_border - 1; + adjusted_mode->CrtcVBlankEnd = adjusted_mode->CrtcVTotal - + top_border - 1; + adjusted_mode->CrtcVSyncStart = adjusted_mode->CrtcVBlankStart; + adjusted_mode->CrtcVSyncEnd = adjusted_mode->CrtcVBlankEnd; + border = 1; + } else { /* Aspects match, let hw scale both directions */ + pfit_control |= VERT_AUTO_SCALE | HORIZ_AUTO_SCALE | + VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR; + } + + horiz_bits = 0.5 + (1 << bits) * horiz_ratio; + vert_bits = 0.5 + (1 << bits) * vert_ratio; + + pfit_pgm_ratios = (((vert_bits << PFIT_VERT_SCALE_SHIFT) & + PFIT_VERT_SCALE_MASK) | + ((horiz_bits << PFIT_HORIZ_SCALE_SHIFT) & + PFIT_HORIZ_SCALE_MASK)); + } + break; + case FULL: + /* + * Full scaling, even if it changes the aspect ratio. Fortunately + * this is all done for us in hw. + */ + pfit_control |= PFIT_ENABLE; + if (IS_I965G(pI830)) + pfit_control |= PFIT_SCALING_AUTO; + else + pfit_control |= VERT_AUTO_SCALE | HORIZ_AUTO_SCALE | + VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR; + break; + default: + /* shouldn't happen */ + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "error: bad fitting mode\n"); + break; + } + +out: + dev_priv->pfit_control = pfit_control; + dev_priv->pfit_pgm_ratios = pfit_pgm_ratios; + dev_priv->need_border = border; + /* XXX: It would be nice to support lower refresh rates on the * panels to reduce power consumption, and perhaps match the * user's requested refresh rate. @@ -527,42 +732,43 @@ i830_lvds_mode_fixup(xf86OutputPtr output, DisplayModePtr mode, } static void -i830_lvds_mode_set(xf86OutputPtr output, DisplayModePtr mode, - DisplayModePtr adjusted_mode) +i830_lvds_prepare(xf86OutputPtr output) { I830OutputPrivatePtr intel_output = output->driver_private; struct i830_lvds_priv *dev_priv = intel_output->dev_priv; ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); - I830CrtcPrivatePtr intel_crtc = output->crtc->driver_private; - uint32_t pfit_control; + uint32_t lvds; - /* The LVDS pin pair will already have been turned on in - * i830_crtc_mode_set since it has a large impact on the DPLL settings. - */ + lvds = INREG(LVDS); - /* Enable automatic panel scaling for non-native modes so that they fill - * the screen. Should be enabled before the pipe is enabled, according to - * register description and PRM. + i830_lvds_dpms(output, DPMSModeOff); + /* + * ->prepare will be called after the CRTC is off but before + * we set the mode, so program the PFIT regs here. */ - if (mode->HDisplay != adjusted_mode->HDisplay || - mode->VDisplay != adjusted_mode->VDisplay) - { - pfit_control = PFIT_ENABLE | - VERT_AUTO_SCALE | HORIZ_AUTO_SCALE | - VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR; - } else { - pfit_control = 0; - } + if (dev_priv->need_border) + OUTREG(LVDS, lvds | LVDS_BORDER_ENABLE); + else + OUTREG(LVDS, lvds & (~LVDS_BORDER_ENABLE)); +} - if (!IS_I965G(pI830)) { - if (dev_priv->panel_wants_dither) - pfit_control |= PANEL_8TO6_DITHER_ENABLE; - } else { - pfit_control |= intel_crtc->pipe << PFIT_PIPE_SHIFT; - } +static void +i830_lvds_mode_set(xf86OutputPtr output, DisplayModePtr mode, + DisplayModePtr adjusted_mode) +{ + I830OutputPrivatePtr intel_output = output->driver_private; + struct i830_lvds_priv *dev_priv = intel_output->dev_priv; + ScrnInfoPtr pScrn = output->scrn; + I830Ptr pI830 = I830PTR(pScrn); - OUTREG(PFIT_CONTROL, pfit_control); + /* + * PFIT must be enabled/disabled while LVDS is on but pipes are still off + */ + OUTREG(PFIT_PGM_RATIOS, dev_priv->pfit_pgm_ratios); + OUTREG(PFIT_CONTROL, dev_priv->pfit_control); + /* It's harmless to turn on the LVDS if it's already on */ + i830_lvds_dpms(output, DPMSModeOn); } /** @@ -653,6 +859,17 @@ static char *backlight_control_names[] = { static Atom backlight_control_atom; static Atom backlight_control_name_atoms[NUM_BACKLIGHT_CONTROL_METHODS]; +#define PANEL_FITTING_NAME "PANEL_FITTING" +#define NUM_PANEL_FITTING_TYPES 3 +static char *panel_fitting_names[] = { + "center", + "full_aspect", + "full", +}; +static Atom panel_fitting_atom; +static Atom panel_fitting_name_atoms[NUM_PANEL_FITTING_TYPES]; + + static int i830_backlight_control_lookup(char *name) { @@ -709,6 +926,18 @@ i830_lvds_set_backlight_control(xf86OutputPtr output) return Success; } + +static int +i830_panel_fitting_lookup(char *name) +{ + int i; + + for (i = 0; i < NUM_PANEL_FITTING_TYPES; i++) + if (!strcmp(name, panel_fitting_names[i])) + return i; + + return -1; +} #endif /* RANDR_12_INTERFACE */ static void @@ -775,6 +1004,33 @@ i830_lvds_create_resources(xf86OutputPtr output) xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "failed to set backlight control, %d\n", err); } + + /* + * Panel fitting control + */ + panel_fitting_atom = MakeAtom(PANEL_FITTING_NAME, + sizeof(PANEL_FITTING_NAME) - 1, TRUE); + for (i = 0; i < NUM_PANEL_FITTING_TYPES; i++) { + panel_fitting_name_atoms[i] = MakeAtom(panel_fitting_names[i], + strlen(panel_fitting_names[i]), + TRUE); + } + err = RRConfigureOutputProperty(output->randr_output, + panel_fitting_atom, TRUE, FALSE, FALSE, + NUM_PANEL_FITTING_TYPES, + (INT32 *)panel_fitting_name_atoms); + if (err != 0) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "RRConfigureOutputProperty error, %d\n", err); + } + err = RRChangeOutputProperty(output->randr_output, panel_fitting_atom, + XA_ATOM, 32, PropModeReplace, 1, + &panel_fitting_name_atoms[dev_priv->fitting_mode], + FALSE, TRUE); + if (err != 0) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "failed to set panel fitting mode, %d\n", err); + } #endif /* RANDR_12_INTERFACE */ } @@ -846,6 +1102,36 @@ i830_lvds_set_property(xf86OutputPtr output, Atom property, "RRChangeOutputProperty error, %d\n", ret); } return TRUE; + } else if (property == panel_fitting_atom) { + Atom atom; + char *name; + int ret; + + if (value->type != XA_ATOM || value->format != 32 || value->size != 1) + return FALSE; + + memcpy(&atom, value->data, 4); + name = NameForAtom(atom); + + ret = i830_panel_fitting_lookup(name); + if (ret < 0) + return FALSE; + + dev_priv->fitting_mode = ret; + + if (output->crtc) { + xf86CrtcPtr crtc = output->crtc; + if (crtc->enabled) { + if (!xf86CrtcSetMode(crtc, &crtc->desiredMode, + crtc->desiredRotation, + crtc->desiredX, crtc->desiredY)) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "Failed to set mode after panel fitting change!\n"); + return FALSE; + } + } + } + return TRUE; } return TRUE; @@ -886,7 +1172,7 @@ static const xf86OutputFuncsRec i830_lvds_output_funcs = { .restore = i830_lvds_restore, .mode_valid = i830_lvds_mode_valid, .mode_fixup = i830_lvds_mode_fixup, - .prepare = i830_output_prepare, + .prepare = i830_lvds_prepare, .mode_set = i830_lvds_mode_set, .commit = i830_output_commit, .detect = i830_lvds_detect, @@ -1075,6 +1361,12 @@ i830_lvds_init(ScrnInfoPtr pScrn) dev_priv->backlight_duty_cycle = dev_priv->get_backlight(output); + /* + * Default to filling the whole screen if the mode is less than the + * native size, without breaking aspect ratio. + */ + dev_priv->fitting_mode = FULL_ASPECT; + return; disable_exit: