1392 lines
40 KiB
C
1392 lines
40 KiB
C
/*
|
|
* Copyright © 2006 Intel Corporation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* Authors:
|
|
* Eric Anholt <eric@anholt.net>
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "xf86.h"
|
|
#include "i830.h"
|
|
#include "i830_bios.h"
|
|
#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 panel is in DPMS off */
|
|
Bool dpmsoff;
|
|
|
|
/* restore backlight to this value */
|
|
int backlight_duty_cycle;
|
|
|
|
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"
|
|
|
|
/*
|
|
* List of available kernel interfaces in priority order
|
|
*/
|
|
static char *backlight_interfaces[] = {
|
|
"asus-laptop",
|
|
"eeepc",
|
|
"thinkpad_screen",
|
|
"acpi_video1",
|
|
"acpi_video0",
|
|
"fujitsu-laptop",
|
|
NULL,
|
|
};
|
|
|
|
/*
|
|
* Must be long enough for BACKLIGHT_CLASS + '/' + longest in above table +
|
|
* '/' + "max_backlight"
|
|
*/
|
|
#define BACKLIGHT_PATH_LEN 80
|
|
/* Enough for 8 digits of backlight + '\n' + '\0' */
|
|
#define BACKLIGHT_VALUE_LEN 10
|
|
|
|
static int backlight_index;
|
|
|
|
static Bool
|
|
i830_kernel_backlight_available(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
struct stat buf;
|
|
char path[BACKLIGHT_PATH_LEN];
|
|
int i;
|
|
|
|
for (i = 0; backlight_interfaces[i] != NULL; i++) {
|
|
sprintf(path, "%s/%s", BACKLIGHT_CLASS, backlight_interfaces[i]);
|
|
if (!stat(path, &buf)) {
|
|
backlight_index = i;
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "found backlight control "
|
|
"method %s\n", path);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Try to figure out which backlight control method to use */
|
|
static void
|
|
i830_set_lvds_backlight_method(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
uint32_t blc_pwm_ctl, blc_pwm_ctl2;
|
|
enum backlight_control method = BCM_NATIVE; /* Default to native */
|
|
|
|
if (i830_kernel_backlight_available(output)) {
|
|
method = BCM_KERNEL;
|
|
} else if (IS_I965GM(pI830) || IS_GM45(pI830)) {
|
|
blc_pwm_ctl2 = INREG(BLC_PWM_CTL2);
|
|
if (blc_pwm_ctl2 & BLM_LEGACY_MODE2)
|
|
method = BCM_COMBO;
|
|
} else {
|
|
blc_pwm_ctl = INREG(BLC_PWM_CTL);
|
|
if (blc_pwm_ctl & BLM_LEGACY_MODE)
|
|
method = BCM_COMBO;
|
|
}
|
|
|
|
pI830->backlight_control_method = method;
|
|
}
|
|
|
|
/*
|
|
* Native methods
|
|
*/
|
|
static void
|
|
i830_lvds_set_backlight_native(xf86OutputPtr output, int level)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
uint32_t blc_pwm_ctl;
|
|
|
|
blc_pwm_ctl = INREG(BLC_PWM_CTL);
|
|
blc_pwm_ctl &= ~BACKLIGHT_DUTY_CYCLE_MASK;
|
|
OUTREG(BLC_PWM_CTL, blc_pwm_ctl | (level << BACKLIGHT_DUTY_CYCLE_SHIFT));
|
|
}
|
|
|
|
static int
|
|
i830_lvds_get_backlight_native(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
uint32_t blc_pwm_ctl;
|
|
|
|
blc_pwm_ctl = INREG(BLC_PWM_CTL);
|
|
blc_pwm_ctl &= BACKLIGHT_DUTY_CYCLE_MASK;
|
|
return blc_pwm_ctl;
|
|
}
|
|
|
|
static int
|
|
i830_lvds_get_backlight_max_native(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
uint32_t pwm_ctl = INREG(BLC_PWM_CTL);
|
|
int val;
|
|
|
|
if (IS_I965GM(pI830) || IS_GM45(pI830)) {
|
|
val = ((pwm_ctl & BACKLIGHT_MODULATION_FREQ_MASK2) >>
|
|
BACKLIGHT_MODULATION_FREQ_SHIFT2);
|
|
} else {
|
|
val = ((pwm_ctl & BACKLIGHT_MODULATION_FREQ_MASK) >>
|
|
BACKLIGHT_MODULATION_FREQ_SHIFT) * 2;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
/*
|
|
* Legacy methods
|
|
*/
|
|
static void
|
|
i830_lvds_set_backlight_legacy(xf86OutputPtr output, int level)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
|
|
#if XSERVER_LIBPCIACCESS
|
|
pci_device_cfg_write_u8(pI830->PciInfo, level,
|
|
LEGACY_BACKLIGHT_BRIGHTNESS);
|
|
#else
|
|
pciWriteByte(pI830->PciTag, LEGACY_BACKLIGHT_BRIGHTNESS, level);
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
i830_lvds_get_backlight_legacy(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
uint8_t lbb;
|
|
|
|
#if XSERVER_LIBPCIACCESS
|
|
pci_device_cfg_read_u8(pI830->PciInfo, &lbb, LEGACY_BACKLIGHT_BRIGHTNESS);
|
|
#else
|
|
lbb = pciReadByte(pI830->PciTag, LEGACY_BACKLIGHT_BRIGHTNESS);
|
|
#endif
|
|
|
|
return lbb;
|
|
}
|
|
|
|
/*
|
|
* Combo methods
|
|
*/
|
|
static void
|
|
i830_lvds_set_backlight_combo(xf86OutputPtr output, int level)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
uint32_t blc_pwm_ctl;
|
|
uint8_t lbb;
|
|
|
|
#if XSERVER_LIBPCIACCESS
|
|
pci_device_cfg_read_u8(pI830->PciInfo, &lbb, LEGACY_BACKLIGHT_BRIGHTNESS);
|
|
#else
|
|
lbb = pciReadByte(pI830->PciTag, LEGACY_BACKLIGHT_BRIGHTNESS);
|
|
#endif
|
|
/*
|
|
* If LBB is zero and we're shooting for a non-zero brightness level,
|
|
* we have to increase LBB by at least 1.
|
|
*/
|
|
if (!lbb && level) {
|
|
#if XSERVER_LIBPCIACCESS
|
|
pci_device_cfg_write_u8(pI830->PciInfo, 1,
|
|
LEGACY_BACKLIGHT_BRIGHTNESS);
|
|
#else
|
|
pciWriteByte(pI830->PciTag, LEGACY_BACKLIGHT_BRIGHTNESS, 1);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Don't set the lowest bit in combo configs since it can act as a flag for
|
|
* max brightness.
|
|
*/
|
|
level <<= 1;
|
|
|
|
blc_pwm_ctl = INREG(BLC_PWM_CTL);
|
|
blc_pwm_ctl &= ~BACKLIGHT_DUTY_CYCLE_MASK;
|
|
OUTREG(BLC_PWM_CTL, blc_pwm_ctl | (level << BACKLIGHT_DUTY_CYCLE_SHIFT));
|
|
}
|
|
|
|
static int
|
|
i830_lvds_get_backlight_combo(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
uint32_t blc_pwm_ctl;
|
|
|
|
blc_pwm_ctl = INREG(BLC_PWM_CTL);
|
|
blc_pwm_ctl &= BACKLIGHT_DUTY_CYCLE_MASK;
|
|
|
|
/* Since we don't use the low bit when using combo, the value is halved */
|
|
|
|
return blc_pwm_ctl >> 1;
|
|
}
|
|
|
|
static int
|
|
i830_lvds_get_backlight_max_combo(xf86OutputPtr output)
|
|
{
|
|
/* Since we don't set the low bit when using combo, the range is halved */
|
|
return i830_lvds_get_backlight_max_native(output) >> 1;
|
|
}
|
|
|
|
/*
|
|
* Kernel methods
|
|
*/
|
|
static void
|
|
i830_lvds_set_backlight_kernel(xf86OutputPtr output, int level)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
char path[BACKLIGHT_PATH_LEN], val[BACKLIGHT_VALUE_LEN];
|
|
int fd, len, ret;
|
|
|
|
len = snprintf(val, BACKLIGHT_VALUE_LEN, "%d\n", level);
|
|
if (len > BACKLIGHT_VALUE_LEN) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "backlight value too large: %d\n",
|
|
level);
|
|
return;
|
|
}
|
|
|
|
sprintf(path, "%s/%s/brightness", BACKLIGHT_CLASS,
|
|
backlight_interfaces[backlight_index]);
|
|
fd = open(path, O_RDWR);
|
|
if (fd == -1) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "failed to open %s for backlight "
|
|
"control: %s\n", path, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
ret = write(fd, val, len);
|
|
if (ret == -1) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "write to %s for backlight "
|
|
"control failed: %s\n", path, strerror(errno));
|
|
}
|
|
|
|
close(fd);
|
|
}
|
|
|
|
static int
|
|
i830_lvds_get_backlight_kernel(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
char path[BACKLIGHT_PATH_LEN], val[BACKLIGHT_VALUE_LEN];
|
|
int fd;
|
|
|
|
sprintf(path, "%s/%s/actual_brightness", BACKLIGHT_CLASS,
|
|
backlight_interfaces[backlight_index]);
|
|
fd = open(path, O_RDONLY);
|
|
if (fd == -1) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "failed to open %s for backlight "
|
|
"control: %s\n", path, strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
memset(val, 0, sizeof(val));
|
|
if (read(fd, val, BACKLIGHT_VALUE_LEN) == -1)
|
|
goto out_err;
|
|
|
|
close(fd);
|
|
return atoi(val);
|
|
|
|
out_err:
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
i830_lvds_get_backlight_max_kernel(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
char path[BACKLIGHT_PATH_LEN], val[BACKLIGHT_VALUE_LEN];
|
|
int fd, max = 0;
|
|
|
|
sprintf(path, "%s/%s/max_brightness", BACKLIGHT_CLASS,
|
|
backlight_interfaces[backlight_index]);
|
|
fd = open(path, O_RDONLY);
|
|
if (fd == -1) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "failed to open %s for backlight "
|
|
"control: %s\n", path, strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
if (read(fd, val, BACKLIGHT_VALUE_LEN) == -1)
|
|
goto out_err;
|
|
|
|
close(fd);
|
|
|
|
max = atoi(val);
|
|
|
|
return max;
|
|
|
|
out_err:
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Sets the power state for the panel.
|
|
*/
|
|
static void
|
|
i830SetLVDSPanelPower(xf86OutputPtr output, Bool on)
|
|
{
|
|
I830OutputPrivatePtr intel_output = output->driver_private;
|
|
struct i830_lvds_priv *dev_priv = intel_output->dev_priv;
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
uint32_t pp_status;
|
|
|
|
if (on) {
|
|
/* if we're going from on->on, be aware to current level. */
|
|
if ((INREG(PP_CONTROL) & POWER_TARGET_ON) && !dev_priv->dpmsoff)
|
|
dev_priv->backlight_duty_cycle = dev_priv->get_backlight(output);
|
|
|
|
/*
|
|
* If we're going from off->on we may need to turn on the backlight.
|
|
* We should use the saved value whenever possible, but on some
|
|
* machines 0 is a valid backlight value (due to an external backlight
|
|
* controller for example), so on them, when turning LVDS back on,
|
|
* they'll always re-maximize the brightness.
|
|
*/
|
|
if (!(INREG(PP_CONTROL) & POWER_TARGET_ON) &&
|
|
dev_priv->backlight_duty_cycle == 0)
|
|
dev_priv->backlight_duty_cycle = dev_priv->backlight_max;
|
|
|
|
OUTREG(PP_CONTROL, INREG(PP_CONTROL) | POWER_TARGET_ON);
|
|
do {
|
|
pp_status = INREG(PP_STATUS);
|
|
} while ((pp_status & PP_ON) == 0);
|
|
|
|
dev_priv->set_backlight(output, dev_priv->backlight_duty_cycle);
|
|
dev_priv->dpmsoff = FALSE;
|
|
} else {
|
|
/*
|
|
* Only save the current backlight value if we're going from
|
|
* on to off.
|
|
*/
|
|
if ((INREG(PP_CONTROL) & POWER_TARGET_ON) && !dev_priv->dpmsoff)
|
|
dev_priv->backlight_duty_cycle = dev_priv->get_backlight(output);
|
|
dev_priv->set_backlight(output, 0);
|
|
|
|
OUTREG(PP_CONTROL, INREG(PP_CONTROL) & ~POWER_TARGET_ON);
|
|
do {
|
|
pp_status = INREG(PP_STATUS);
|
|
} while (pp_status & PP_ON);
|
|
|
|
dev_priv->dpmsoff = TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
i830_lvds_dpms (xf86OutputPtr output, int mode)
|
|
{
|
|
if (mode == DPMSModeOn)
|
|
i830SetLVDSPanelPower(output, TRUE);
|
|
else
|
|
i830SetLVDSPanelPower(output, FALSE);
|
|
|
|
/* XXX: We never power down the LVDS pairs. */
|
|
}
|
|
|
|
static void
|
|
i830_lvds_save (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);
|
|
|
|
if (IS_I965GM(pI830) || IS_GM45(pI830))
|
|
pI830->saveBLC_PWM_CTL2 = INREG(BLC_PWM_CTL2);
|
|
pI830->savePP_ON = INREG(PP_ON_DELAYS);
|
|
pI830->savePP_OFF = INREG(PP_OFF_DELAYS);
|
|
pI830->savePP_CONTROL = INREG(PP_CONTROL);
|
|
pI830->savePP_DIVISOR = INREG(PP_DIVISOR);
|
|
pI830->saveBLC_PWM_CTL = INREG(BLC_PWM_CTL);
|
|
if ((INREG(PP_CONTROL) & POWER_TARGET_ON) && !dev_priv->dpmsoff)
|
|
dev_priv->backlight_duty_cycle = dev_priv->get_backlight(output);
|
|
}
|
|
|
|
static void
|
|
i830_lvds_restore(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
|
|
if (IS_I965GM(pI830) || IS_GM45(pI830))
|
|
OUTREG(BLC_PWM_CTL2, pI830->saveBLC_PWM_CTL2);
|
|
OUTREG(BLC_PWM_CTL, pI830->saveBLC_PWM_CTL);
|
|
OUTREG(PP_ON_DELAYS, pI830->savePP_ON);
|
|
OUTREG(PP_OFF_DELAYS, pI830->savePP_OFF);
|
|
OUTREG(PP_DIVISOR, pI830->savePP_DIVISOR);
|
|
OUTREG(PP_CONTROL, pI830->savePP_CONTROL);
|
|
if (pI830->savePP_CONTROL & POWER_TARGET_ON)
|
|
i830SetLVDSPanelPower(output, TRUE);
|
|
else
|
|
i830SetLVDSPanelPower(output, FALSE);
|
|
}
|
|
|
|
static int
|
|
i830_lvds_mode_valid(xf86OutputPtr output, DisplayModePtr pMode)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
DisplayModePtr pFixedMode = pI830->lvds_fixed_mode;
|
|
|
|
if (pFixedMode)
|
|
{
|
|
if (pMode->HDisplay > pFixedMode->HDisplay)
|
|
return MODE_PANEL;
|
|
if (pMode->VDisplay > pFixedMode->VDisplay)
|
|
return MODE_PANEL;
|
|
}
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static Bool
|
|
i830_lvds_mode_fixup(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;
|
|
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];
|
|
|
|
if (other_output != output && other_output->crtc == output->crtc) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"Can't enable LVDS and another output on the same "
|
|
"pipe\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (intel_crtc->pipe == 0) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"Can't support LVDS on pipe A\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/* If we don't have a panel mode there's not much we can do */
|
|
if (pI830->lvds_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.
|
|
*/
|
|
adjusted_mode->HDisplay = pI830->lvds_fixed_mode->HDisplay;
|
|
adjusted_mode->HSyncStart = pI830->lvds_fixed_mode->HSyncStart;
|
|
adjusted_mode->HSyncEnd = pI830->lvds_fixed_mode->HSyncEnd;
|
|
adjusted_mode->HTotal = pI830->lvds_fixed_mode->HTotal;
|
|
adjusted_mode->VDisplay = pI830->lvds_fixed_mode->VDisplay;
|
|
adjusted_mode->VSyncStart = pI830->lvds_fixed_mode->VSyncStart;
|
|
adjusted_mode->VSyncEnd = pI830->lvds_fixed_mode->VSyncEnd;
|
|
adjusted_mode->VTotal = pI830->lvds_fixed_mode->VTotal;
|
|
adjusted_mode->Clock = pI830->lvds_fixed_mode->Clock;
|
|
xf86SetModeCrtc(adjusted_mode, INTERLACE_HALVE_V);
|
|
|
|
/* Make sure pre-965s set dither correctly */
|
|
if (!IS_I965G(pI830) && pI830->lvds_dither)
|
|
pfit_control |= PANEL_8TO6_DITHER_ENABLE;
|
|
|
|
/* Native modes don't need fitting */
|
|
if (adjusted_mode->HDisplay == mode->HDisplay &&
|
|
adjusted_mode->VDisplay == mode->VDisplay) {
|
|
pfit_pgm_ratios = 0;
|
|
border = 0;
|
|
goto out;
|
|
}
|
|
|
|
/* 965+ wants fuzzy fitting */
|
|
if (IS_I965G(pI830))
|
|
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 =
|
|
(pI830->lvds_fixed_mode->HDisplay - mode->HDisplay) / 2;
|
|
right_border = left_border;
|
|
if (mode->HDisplay & 1)
|
|
right_border++;
|
|
top_border =
|
|
(pI830->lvds_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 = (pI830->lvds_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 = (pI830->lvds_fixed_mode->VDisplay -
|
|
scaled_height) / 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;
|
|
|
|
if (border)
|
|
intel_output->lvds_bits |= LVDS_BORDER_ENABLE;
|
|
else
|
|
intel_output->lvds_bits &= ~LVDS_BORDER_ENABLE;
|
|
/* 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.
|
|
*/
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
i830_lvds_prepare(xf86OutputPtr output)
|
|
{
|
|
i830_lvds_dpms(output, DPMSModeOff);
|
|
}
|
|
|
|
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);
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Detect the LVDS connection.
|
|
*
|
|
* This always returns OUTPUT_STATUS_CONNECTED. This output should only have
|
|
* been set up if the LVDS was actually connected anyway.
|
|
*/
|
|
static xf86OutputStatus
|
|
i830_lvds_detect(xf86OutputPtr output)
|
|
{
|
|
return XF86OutputStatusConnected;
|
|
}
|
|
|
|
/**
|
|
* Return the list of DDC modes if available, or the BIOS fixed mode otherwise.
|
|
*/
|
|
static DisplayModePtr
|
|
i830_lvds_get_modes(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
I830OutputPrivatePtr intel_output = output->driver_private;
|
|
xf86MonPtr edid_mon;
|
|
DisplayModePtr modes;
|
|
|
|
edid_mon = xf86OutputGetEDID (output, intel_output->pDDCBus);
|
|
xf86OutputSetEDID (output, edid_mon);
|
|
|
|
modes = xf86OutputGetEDIDModes (output);
|
|
if (modes != NULL)
|
|
return modes;
|
|
|
|
if (!output->MonInfo)
|
|
{
|
|
edid_mon = xcalloc (1, sizeof (xf86Monitor));
|
|
if (edid_mon)
|
|
{
|
|
/* Set wide sync ranges so we get all modes
|
|
* handed to valid_mode for checking
|
|
*/
|
|
edid_mon->det_mon[0].type = DS_RANGES;
|
|
edid_mon->det_mon[0].section.ranges.min_v = 0;
|
|
edid_mon->det_mon[0].section.ranges.max_v = 200;
|
|
edid_mon->det_mon[0].section.ranges.min_h = 0;
|
|
edid_mon->det_mon[0].section.ranges.max_h = 200;
|
|
|
|
output->MonInfo = edid_mon;
|
|
}
|
|
}
|
|
|
|
if (pI830->lvds_fixed_mode != NULL)
|
|
return xf86DuplicateMode(pI830->lvds_fixed_mode);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
i830_lvds_destroy (xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
I830OutputPrivatePtr intel_output = output->driver_private;
|
|
|
|
xf86DeleteMode (&pI830->lvds_fixed_mode, pI830->lvds_fixed_mode);
|
|
if (intel_output)
|
|
xfree (intel_output);
|
|
}
|
|
|
|
#ifdef RANDR_12_INTERFACE
|
|
#define BACKLIGHT_NAME "BACKLIGHT"
|
|
static Atom backlight_atom;
|
|
|
|
/*
|
|
* Backlight control lets the user select how the driver should manage
|
|
* backlight changes: using the legacy interface, the native interface,
|
|
* or not at all.
|
|
*/
|
|
#define BACKLIGHT_CONTROL_NAME "BACKLIGHT_CONTROL"
|
|
#define NUM_BACKLIGHT_CONTROL_METHODS 4
|
|
static char *backlight_control_names[] = {
|
|
"native",
|
|
"legacy",
|
|
"combination",
|
|
"kernel",
|
|
};
|
|
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)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_BACKLIGHT_CONTROL_METHODS; i++)
|
|
if (!strcmp(name, backlight_control_names[i]))
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static Bool
|
|
i830_lvds_set_backlight_control(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
I830OutputPrivatePtr intel_output = output->driver_private;
|
|
struct i830_lvds_priv *dev_priv = intel_output->dev_priv;
|
|
|
|
switch (pI830->backlight_control_method) {
|
|
case BCM_NATIVE:
|
|
dev_priv->set_backlight = i830_lvds_set_backlight_native;
|
|
dev_priv->get_backlight = i830_lvds_get_backlight_native;
|
|
dev_priv->backlight_max =
|
|
i830_lvds_get_backlight_max_native(output);
|
|
break;
|
|
case BCM_LEGACY:
|
|
dev_priv->set_backlight = i830_lvds_set_backlight_legacy;
|
|
dev_priv->get_backlight = i830_lvds_get_backlight_legacy;
|
|
dev_priv->backlight_max = 0xff;
|
|
break;
|
|
case BCM_COMBO:
|
|
dev_priv->set_backlight = i830_lvds_set_backlight_combo;
|
|
dev_priv->get_backlight = i830_lvds_get_backlight_combo;
|
|
dev_priv->backlight_max =
|
|
i830_lvds_get_backlight_max_combo(output);
|
|
break;
|
|
case BCM_KERNEL:
|
|
dev_priv->set_backlight = i830_lvds_set_backlight_kernel;
|
|
dev_priv->get_backlight = i830_lvds_get_backlight_kernel;
|
|
dev_priv->backlight_max =
|
|
i830_lvds_get_backlight_max_kernel(output);
|
|
break;
|
|
default:
|
|
/*
|
|
* Should be impossible to get here unless the caller set a bogus
|
|
* backlight_control_method
|
|
*/
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "error: bad backlight control "
|
|
"method\n");
|
|
break;
|
|
}
|
|
|
|
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
|
|
i830_lvds_create_resources(xf86OutputPtr output)
|
|
{
|
|
#ifdef RANDR_12_INTERFACE
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
I830OutputPrivatePtr intel_output = output->driver_private;
|
|
struct i830_lvds_priv *dev_priv = intel_output->dev_priv;
|
|
INT32 backlight_range[2];
|
|
int data, err, i;
|
|
|
|
/* Set up the backlight property, which takes effect immediately
|
|
* and accepts values only within the backlight_range.
|
|
*
|
|
* XXX: Currently, RandR doesn't verify that properties set are
|
|
* within the backlight_range.
|
|
*/
|
|
backlight_atom = MakeAtom(BACKLIGHT_NAME, sizeof(BACKLIGHT_NAME) - 1,
|
|
TRUE);
|
|
|
|
backlight_range[0] = 0;
|
|
backlight_range[1] = dev_priv->backlight_max;
|
|
err = RRConfigureOutputProperty(output->randr_output, backlight_atom,
|
|
FALSE, TRUE, FALSE, 2, backlight_range);
|
|
if (err != 0) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"RRConfigureOutputProperty error, %d\n", err);
|
|
}
|
|
/* Set the current value of the backlight property */
|
|
data = dev_priv->backlight_duty_cycle;
|
|
err = RRChangeOutputProperty(output->randr_output, backlight_atom,
|
|
XA_INTEGER, 32, PropModeReplace, 1, &data,
|
|
FALSE, TRUE);
|
|
if (err != 0) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"RRChangeOutputProperty error, %d\n", err);
|
|
}
|
|
|
|
/*
|
|
* Now setup the control selection property
|
|
*/
|
|
backlight_control_atom = MakeAtom(BACKLIGHT_CONTROL_NAME,
|
|
sizeof(BACKLIGHT_CONTROL_NAME) - 1, TRUE);
|
|
for (i = 0; i < NUM_BACKLIGHT_CONTROL_METHODS; i++) {
|
|
backlight_control_name_atoms[i] =
|
|
MakeAtom(backlight_control_names[i],
|
|
strlen(backlight_control_names[i]), TRUE);
|
|
}
|
|
err = RRConfigureOutputProperty(output->randr_output,
|
|
backlight_control_atom, TRUE, FALSE, FALSE,
|
|
NUM_BACKLIGHT_CONTROL_METHODS,
|
|
(INT32 *)backlight_control_name_atoms);
|
|
if (err != 0) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"RRConfigureOutputProperty error, %d\n", err);
|
|
}
|
|
err = RRChangeOutputProperty(output->randr_output, backlight_control_atom,
|
|
XA_ATOM, 32, PropModeReplace, 1,
|
|
&backlight_control_name_atoms[pI830->backlight_control_method],
|
|
FALSE, TRUE);
|
|
if (err != 0) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"failed to set backlight control, %d\n", err);
|
|
}
|
|
|
|
/*
|
|
* Panel fitting control
|
|
*/
|
|
|
|
/* Disable panel fitting setting on untested pre-915 chips */
|
|
if (!IS_I9XX(pI830) && !(pI830->quirk_flag & QUIRK_PFIT_SAFE))
|
|
return;
|
|
|
|
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 */
|
|
}
|
|
|
|
#ifdef RANDR_12_INTERFACE
|
|
static Bool
|
|
i830_lvds_set_property(xf86OutputPtr output, Atom property,
|
|
RRPropertyValuePtr value)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
I830OutputPrivatePtr intel_output = output->driver_private;
|
|
struct i830_lvds_priv *dev_priv = intel_output->dev_priv;
|
|
|
|
if (property == backlight_atom) {
|
|
INT32 val;
|
|
|
|
if (value->type != XA_INTEGER || value->format != 32 ||
|
|
value->size != 1)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
val = *(INT32 *)value->data;
|
|
if (val < 0 || val > dev_priv->backlight_max)
|
|
return FALSE;
|
|
|
|
if (val != dev_priv->backlight_duty_cycle) {
|
|
dev_priv->set_backlight(output, val);
|
|
dev_priv->backlight_duty_cycle = val;
|
|
}
|
|
return TRUE;
|
|
} else if (property == backlight_control_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_backlight_control_lookup(name);
|
|
if (ret < 0)
|
|
return FALSE;
|
|
|
|
pI830->backlight_control_method = ret;
|
|
i830_lvds_set_backlight_control(output);
|
|
|
|
/*
|
|
* Update the backlight atom since the range and value may have changed
|
|
*/
|
|
backlight_range[0] = 0;
|
|
backlight_range[1] = dev_priv->backlight_max;
|
|
ret = RRConfigureOutputProperty(output->randr_output, backlight_atom,
|
|
FALSE, TRUE, FALSE, 2, backlight_range);
|
|
if (ret != 0) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"RRConfigureOutputProperty error, %d\n", ret);
|
|
}
|
|
/* Set the current value of the backlight property */
|
|
if ((INREG(PP_CONTROL) & POWER_TARGET_ON) && !dev_priv->dpmsoff)
|
|
data = dev_priv->get_backlight(output);
|
|
else
|
|
data = dev_priv->backlight_duty_cycle;
|
|
ret = RRChangeOutputProperty(output->randr_output, backlight_atom,
|
|
XA_INTEGER, 32, PropModeReplace, 1, &data,
|
|
FALSE, TRUE);
|
|
if (ret != 0) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"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;
|
|
|
|
if (dev_priv->fitting_mode == ret)
|
|
return TRUE;
|
|
|
|
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;
|
|
}
|
|
#endif /* RANDR_12_INTERFACE */
|
|
|
|
#ifdef RANDR_13_INTERFACE
|
|
static Bool
|
|
i830_lvds_get_property(xf86OutputPtr output, Atom property)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
I830OutputPrivatePtr intel_output = output->driver_private;
|
|
struct i830_lvds_priv *dev_priv = intel_output->dev_priv;
|
|
int ret;
|
|
|
|
/*
|
|
* Only need to update properties that might change out from under
|
|
* us. The others will be cached by the randr core code.
|
|
*/
|
|
if (property == backlight_atom) {
|
|
int val;
|
|
if ((INREG(PP_CONTROL) & POWER_TARGET_ON) && !dev_priv->dpmsoff) {
|
|
val = dev_priv->get_backlight(output);
|
|
dev_priv->backlight_duty_cycle = val;
|
|
} else
|
|
val = dev_priv->backlight_duty_cycle;
|
|
ret = RRChangeOutputProperty(output->randr_output, backlight_atom,
|
|
XA_INTEGER, 32, PropModeReplace, 1, &val,
|
|
FALSE, TRUE);
|
|
if (ret != Success)
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
#endif /* RANDR_13_INTERFACE */
|
|
|
|
#ifdef RANDR_GET_CRTC_INTERFACE
|
|
static xf86CrtcPtr
|
|
i830_lvds_get_crtc(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
int pipe = !!(INREG(LVDS) & LVDS_PIPEB_SELECT);
|
|
|
|
return i830_pipe_to_crtc(pScrn, pipe);
|
|
}
|
|
#endif
|
|
|
|
static const xf86OutputFuncsRec i830_lvds_output_funcs = {
|
|
.create_resources = i830_lvds_create_resources,
|
|
.dpms = i830_lvds_dpms,
|
|
.save = i830_lvds_save,
|
|
.restore = i830_lvds_restore,
|
|
.mode_valid = i830_lvds_mode_valid,
|
|
.mode_fixup = i830_lvds_mode_fixup,
|
|
.prepare = i830_lvds_prepare,
|
|
.mode_set = i830_lvds_mode_set,
|
|
.commit = i830_output_commit,
|
|
.detect = i830_lvds_detect,
|
|
.get_modes = i830_lvds_get_modes,
|
|
#ifdef RANDR_12_INTERFACE
|
|
.set_property = i830_lvds_set_property,
|
|
#endif
|
|
#ifdef RANDR_13_INTERFACE
|
|
.get_property = i830_lvds_get_property,
|
|
#endif
|
|
.destroy = i830_lvds_destroy,
|
|
#ifdef RANDR_GET_CRTC_INTERFACE
|
|
.get_crtc = i830_lvds_get_crtc,
|
|
#endif
|
|
};
|
|
|
|
void
|
|
i830_lvds_init(ScrnInfoPtr pScrn)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
xf86OutputPtr output;
|
|
I830OutputPrivatePtr intel_output;
|
|
DisplayModePtr modes, scan;
|
|
DisplayModePtr lvds_ddc_mode = NULL;
|
|
struct i830_lvds_priv *dev_priv;
|
|
|
|
if (pI830->quirk_flag & QUIRK_IGNORE_LVDS)
|
|
return;
|
|
|
|
output = xf86OutputCreate (pScrn, &i830_lvds_output_funcs, "LVDS");
|
|
if (!output)
|
|
return;
|
|
intel_output = xnfcalloc (sizeof (I830OutputPrivateRec) +
|
|
sizeof (struct i830_lvds_priv), 1);
|
|
if (!intel_output)
|
|
{
|
|
xf86OutputDestroy (output);
|
|
return;
|
|
}
|
|
intel_output->type = I830_OUTPUT_LVDS;
|
|
intel_output->pipe_mask = (1 << 1);
|
|
intel_output->clone_mask = (1 << I830_OUTPUT_LVDS);
|
|
|
|
output->driver_private = intel_output;
|
|
output->subpixel_order = SubPixelHorizontalRGB;
|
|
output->interlaceAllowed = FALSE;
|
|
output->doubleScanAllowed = FALSE;
|
|
|
|
dev_priv = (struct i830_lvds_priv *) (intel_output + 1);
|
|
intel_output->dev_priv = dev_priv;
|
|
|
|
/*
|
|
* Mode detection algorithms for LFP:
|
|
* 1) if EDID present, use it, done
|
|
* 2) if VBT present, use it, done
|
|
* 3) if current mode is programmed, use it, done
|
|
* 4) check for Mac mini & other quirks
|
|
* 4) fail, assume no LFP
|
|
*/
|
|
|
|
/* Set up the LVDS DDC channel. Most panels won't support it, but it can
|
|
* be useful if available.
|
|
*/
|
|
I830I2CInit(pScrn, &intel_output->pDDCBus, GPIOC, "LVDSDDC_C");
|
|
|
|
if (pI830->skip_panel_detect) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Skipping any attempt to determine panel fixed mode.\n");
|
|
goto found_mode;
|
|
}
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Attempting to determine panel fixed mode.\n");
|
|
|
|
/* Attempt to get the fixed panel mode from DDC. Assume that the preferred
|
|
* mode is the right one.
|
|
*/
|
|
modes = i830_ddc_get_modes(output);
|
|
for (scan = modes; scan != NULL; scan = scan->next) {
|
|
if (scan->type & M_T_PREFERRED)
|
|
break;
|
|
}
|
|
if (scan != NULL) {
|
|
/* Pull our chosen mode out and make it the fixed mode */
|
|
if (modes == scan)
|
|
modes = modes->next;
|
|
if (scan->prev != NULL)
|
|
scan->prev = scan->next;
|
|
if (scan->next != NULL)
|
|
scan->next = scan->prev;
|
|
lvds_ddc_mode = scan;
|
|
}
|
|
/* Delete the mode list */
|
|
while (modes != NULL)
|
|
xf86DeleteMode(&modes, modes);
|
|
|
|
if (lvds_ddc_mode) {
|
|
pI830->lvds_fixed_mode = lvds_ddc_mode;
|
|
goto found_mode;
|
|
}
|
|
|
|
/* Get the LVDS fixed mode out of the BIOS. We should support LVDS with
|
|
* the BIOS being unavailable or broken, but lack the configuration options
|
|
* for now.
|
|
*/
|
|
if (pI830->lvds_fixed_mode)
|
|
goto found_mode;
|
|
|
|
/* If we *still* don't have a mode, try checking if the panel is already
|
|
* turned on. If so, assume that whatever is currently programmed is the
|
|
* correct mode.
|
|
*/
|
|
if (!pI830->lvds_fixed_mode) {
|
|
uint32_t lvds = INREG(LVDS);
|
|
int pipe = (lvds & LVDS_PIPEB_SELECT) ? 1 : 0;
|
|
xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(pScrn);
|
|
xf86CrtcPtr crtc = xf86_config->crtc[pipe];
|
|
|
|
if (lvds & LVDS_PORT_EN) {
|
|
pI830->lvds_fixed_mode = i830_crtc_mode_get(pScrn, crtc);
|
|
if (pI830->lvds_fixed_mode != NULL) {
|
|
pI830->lvds_fixed_mode->type |= M_T_PREFERRED;
|
|
goto found_mode;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!pI830->lvds_fixed_mode)
|
|
goto disable_exit;
|
|
|
|
found_mode:
|
|
|
|
/* Blacklist machines with BIOSes that list an LVDS panel without actually
|
|
* having one.
|
|
*/
|
|
if (pI830->quirk_flag & QUIRK_IGNORE_MACMINI_LVDS) {
|
|
/* It's a Mac Mini or Macbook Pro.
|
|
*
|
|
* Apple hardware is out to get us. The macbook pro has a real
|
|
* LVDS panel, but the mac mini does not, and they have the same
|
|
* device IDs. We'll distinguish by panel size, on the assumption
|
|
* that Apple isn't about to make any machines with an 800x600
|
|
* display.
|
|
*/
|
|
|
|
if (pI830->lvds_fixed_mode != NULL &&
|
|
pI830->lvds_fixed_mode->HDisplay == 800 &&
|
|
pI830->lvds_fixed_mode->VDisplay == 600)
|
|
{
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Suspected Mac Mini, ignoring the LVDS\n");
|
|
goto disable_exit;
|
|
}
|
|
}
|
|
|
|
i830_set_lvds_backlight_method(output);
|
|
|
|
switch (pI830->backlight_control_method) {
|
|
case BCM_NATIVE:
|
|
dev_priv->set_backlight = i830_lvds_set_backlight_native;
|
|
dev_priv->get_backlight = i830_lvds_get_backlight_native;
|
|
dev_priv->backlight_max = i830_lvds_get_backlight_max_native(output);
|
|
break;
|
|
case BCM_LEGACY:
|
|
dev_priv->set_backlight = i830_lvds_set_backlight_legacy;
|
|
dev_priv->get_backlight = i830_lvds_get_backlight_legacy;
|
|
dev_priv->backlight_max = 0xff;
|
|
break;
|
|
case BCM_COMBO:
|
|
dev_priv->set_backlight = i830_lvds_set_backlight_combo;
|
|
dev_priv->get_backlight = i830_lvds_get_backlight_combo;
|
|
dev_priv->backlight_max = i830_lvds_get_backlight_max_combo(output);
|
|
break;
|
|
case BCM_KERNEL:
|
|
dev_priv->set_backlight = i830_lvds_set_backlight_kernel;
|
|
dev_priv->get_backlight = i830_lvds_get_backlight_kernel;
|
|
dev_priv->backlight_max = i830_lvds_get_backlight_max_kernel(output);
|
|
break;
|
|
default:
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "bad backlight control method\n");
|
|
break;
|
|
}
|
|
|
|
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. (Change default to origin FULL mode, i8xx can only work
|
|
* in that mode for now.)
|
|
*/
|
|
dev_priv->fitting_mode = FULL;
|
|
|
|
return;
|
|
|
|
disable_exit:
|
|
xf86DestroyI2CBusRec(intel_output->pDDCBus, TRUE, TRUE);
|
|
xf86OutputDestroy(output);
|
|
}
|