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 a23631c..d4f1cc3 100644 --- a/src/i830_lvds.c +++ b/src/i830_lvds.c @@ -44,6 +44,18 @@ #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; @@ -57,6 +69,7 @@ struct i830_lvds_priv { void (*set_backlight)(xf86OutputPtr output, int level); int (*get_backlight)(xf86OutputPtr output); int backlight_max; + enum pfit_mode fitting_mode; }; #define BACKLIGHT_CLASS "/sys/class/backlight" @@ -480,6 +493,7 @@ 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; + int left_border = 0, right_border = 0, top_border = 0, bottom_border = 0; int i; for (i = 0; i < xf86_config->num_output; i++) { @@ -515,6 +529,33 @@ i830_lvds_mode_fixup(xf86OutputPtr output, DisplayModePtr mode, adjusted_mode->VTotal = dev_priv->panel_fixed_mode->VTotal; adjusted_mode->Clock = dev_priv->panel_fixed_mode->Clock; xf86SetModeCrtc(adjusted_mode, INTERLACE_HALVE_V); + if (dev_priv->fitting_mode == CENTER) { + /* Calculate borders */ + 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->CrtcHSyncStart = adjusted_mode->CrtcVBlankStart; +// adjusted_mode->CrtcHSyncEnd = adjusted_mode->CrtcVBlankEnd; + } } /* XXX: It would be nice to support lower refresh rates on the @@ -525,6 +566,36 @@ i830_lvds_mode_fixup(xf86OutputPtr output, DisplayModePtr mode, return TRUE; } +static CARD32 +i830_lvds_pgm_ratios(I830Ptr pI830, DisplayModePtr desired_mode, + DisplayModePtr fixed_mode) +{ + CARD32 pgm_ratio; + int bits = IS_I965G(pI830) ? 13 : 12; + float horiz_ratio, vert_ratio; + unsigned long horiz_bits, vert_bits; + + horiz_ratio = ((float)desired_mode->HDisplay + 1) / + ((float)fixed_mode->HDisplay + 1); + vert_ratio = ((float)desired_mode->VDisplay + 1) / + ((float)fixed_mode->VDisplay + 1); + + horiz_bits = (1 << bits) * horiz_ratio; + vert_bits = (1 << bits) * vert_ratio; + + if (IS_I965G(pI830)) + pgm_ratio = (((vert_bits << PFIT_VERT_SCALE_SHIFT_965) & + PFIT_VERT_SCALE_MASK_965) | + ((horiz_bits << PFIT_HORIZ_SCALE_SHIFT_965) & + PFIT_HORIZ_SCALE_MASK_965)); + else + pgm_ratio = (((vert_bits << PFIT_VERT_SCALE_SHIFT) & + PFIT_VERT_SCALE_MASK) | + ((horiz_bits << PFIT_HORIZ_SCALE_SHIFT) & + PFIT_HORIZ_SCALE_MASK)); + return pgm_ratio; +} + static void i830_lvds_mode_set(xf86OutputPtr output, DisplayModePtr mode, DisplayModePtr adjusted_mode) @@ -534,33 +605,88 @@ i830_lvds_mode_set(xf86OutputPtr output, DisplayModePtr mode, ScrnInfoPtr pScrn = output->scrn; I830Ptr pI830 = I830PTR(pScrn); I830CrtcPrivatePtr intel_crtc = output->crtc->driver_private; - uint32_t pfit_control; + uint32_t pfit_control = 0, pfit_pgm_ratios = 0; + float fixed_ratio, desired_ratio; /* 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. */ - /* 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. - */ - 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 the mode is native, we're done */ + if (mode->HDisplay == adjusted_mode->HDisplay || + mode->VDisplay == adjusted_mode->VDisplay) + goto out; + + fixed_ratio = (float)adjusted_mode->HDisplay / + (float)adjusted_mode->VDisplay; + desired_ratio = (float)mode->HDisplay / + (float)mode->VDisplay; 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_control |= (intel_crtc->pipe << PFIT_PIPE_SHIFT) | + PFIT_FILTER_FUZZY; } + + /* 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. + */ + switch (dev_priv->fitting_mode) { + case CENTER: + /* No scaling, 1:1 pixels, centered (see fixup code) */ + /* Set border colors to black */ + OUTREG(BCLRPAT_A, 0); + OUTREG(BCLRPAT_B, 0); + break; + case FULL_ASPECT: + /* Scale but preserve aspect ratio */ + pfit_control |= PFIT_ENABLE; + if (IS_I965G(pI830)) { + if (fixed_ratio > desired_ratio) + pfit_control |= PFIT_SCALING_PILLAR; + else if (fixed_ratio < desired_ratio) + pfit_control |= PFIT_SCALING_LETTER; + else + pfit_control |= PFIT_SCALING_AUTO; + } else { + if (fixed_ratio > desired_ratio) { + pfit_control |= VERT_AUTO_SCALE | VERT_INTERP_BILINEAR | + HORIZ_INTERP_BILINEAR; + pfit_pgm_ratios = + i830_lvds_pgm_ratios(pI830, mode, adjusted_mode); + } else if (fixed_ratio < desired_ratio) { + pfit_control |= HORIZ_AUTO_SCALE | VERT_INTERP_BILINEAR | + HORIZ_INTERP_BILINEAR; + pfit_pgm_ratios = + i830_lvds_pgm_ratios(pI830, mode, adjusted_mode); + } else { + pfit_control |= VERT_AUTO_SCALE | HORIZ_AUTO_SCALE | + VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR; + } + } + break; + case FULL: + /* Full scaling, even if it changes the aspect ratio */ + 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: + if (pfit_pgm_ratios) + OUTREG(PFIT_PGM_RATIOS, pfit_pgm_ratios); OUTREG(PFIT_CONTROL, pfit_control); } @@ -652,6 +778,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) { @@ -708,6 +845,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 @@ -774,6 +923,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 */ } @@ -845,6 +1021,37 @@ i830_lvds_set_property(xf86OutputPtr output, Atom property, "RRChangeOutputProperty error, %d\n", ret); } return TRUE; + } else if (property = panel_fitting_atom) { + INT32 backlight_range[2]; + Atom atom; + char *name; + int ret, data; + + 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; @@ -1074,6 +1281,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: