532 lines
15 KiB
C
532 lines
15 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 "xf86.h"
|
|
#include "i830.h"
|
|
#include "i830_bios.h"
|
|
#include "i830_display.h"
|
|
#include "X11/Xatom.h"
|
|
|
|
/**
|
|
* Sets the backlight level.
|
|
*
|
|
* \param level backlight level, from 0 to i830_lvds_get_max_backlight().
|
|
*/
|
|
static void
|
|
i830_lvds_set_backlight(ScrnInfoPtr pScrn, int level)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
CARD32 blc_pwm_ctl;
|
|
|
|
blc_pwm_ctl = INREG(BLC_PWM_CTL) & ~BACKLIGHT_DUTY_CYCLE_MASK;
|
|
OUTREG(BLC_PWM_CTL, blc_pwm_ctl | (level << BACKLIGHT_DUTY_CYCLE_SHIFT));
|
|
}
|
|
|
|
/**
|
|
* Returns the maximum level of the backlight duty cycle field.
|
|
*/
|
|
static CARD32
|
|
i830_lvds_get_max_backlight(ScrnInfoPtr pScrn)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
|
|
return ((INREG(BLC_PWM_CTL) & BACKLIGHT_MODULATION_FREQ_MASK) >>
|
|
BACKLIGHT_MODULATION_FREQ_SHIFT) * 2;
|
|
}
|
|
|
|
/**
|
|
* Sets the power state for the panel.
|
|
*/
|
|
static void
|
|
i830SetLVDSPanelPower(ScrnInfoPtr pScrn, Bool on)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
CARD32 pp_status;
|
|
|
|
if (on) {
|
|
OUTREG(PP_CONTROL, INREG(PP_CONTROL) | POWER_TARGET_ON);
|
|
do {
|
|
pp_status = INREG(PP_STATUS);
|
|
} while ((pp_status & PP_ON) == 0);
|
|
|
|
i830_lvds_set_backlight(pScrn, pI830->backlight_duty_cycle);
|
|
} else {
|
|
i830_lvds_set_backlight(pScrn, 0);
|
|
|
|
OUTREG(PP_CONTROL, INREG(PP_CONTROL) & ~POWER_TARGET_ON);
|
|
do {
|
|
pp_status = INREG(PP_STATUS);
|
|
} while (pp_status & PP_ON);
|
|
}
|
|
}
|
|
|
|
static void
|
|
i830_lvds_dpms (xf86OutputPtr output, int mode)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
|
|
if (mode == DPMSModeOn)
|
|
i830SetLVDSPanelPower(pScrn, TRUE);
|
|
else
|
|
i830SetLVDSPanelPower(pScrn, FALSE);
|
|
|
|
/* XXX: We never power down the LVDS pair. */
|
|
}
|
|
|
|
static void
|
|
i830_lvds_save (xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
|
|
pI830->savePP_ON = INREG(LVDSPP_ON);
|
|
pI830->savePP_OFF = INREG(LVDSPP_OFF);
|
|
pI830->saveLVDS = INREG(LVDS);
|
|
pI830->savePP_CONTROL = INREG(PP_CONTROL);
|
|
pI830->savePP_CYCLE = INREG(PP_CYCLE);
|
|
pI830->saveBLC_PWM_CTL = INREG(BLC_PWM_CTL);
|
|
pI830->backlight_duty_cycle = (pI830->saveBLC_PWM_CTL &
|
|
BACKLIGHT_DUTY_CYCLE_MASK);
|
|
|
|
/*
|
|
* If the light is off at server startup, just make it full brightness
|
|
*/
|
|
if (pI830->backlight_duty_cycle == 0)
|
|
pI830->backlight_duty_cycle = i830_lvds_get_max_backlight(pScrn);
|
|
}
|
|
|
|
static void
|
|
i830_lvds_restore(xf86OutputPtr output)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
|
|
OUTREG(BLC_PWM_CTL, pI830->saveBLC_PWM_CTL);
|
|
OUTREG(LVDSPP_ON, pI830->savePP_ON);
|
|
OUTREG(LVDSPP_OFF, pI830->savePP_OFF);
|
|
OUTREG(PP_CYCLE, pI830->savePP_CYCLE);
|
|
OUTREG(LVDS, pI830->saveLVDS);
|
|
OUTREG(PP_CONTROL, pI830->savePP_CONTROL);
|
|
if (pI830->savePP_CONTROL & POWER_TARGET_ON)
|
|
i830SetLVDSPanelPower(pScrn, TRUE);
|
|
else
|
|
i830SetLVDSPanelPower(pScrn, FALSE);
|
|
}
|
|
|
|
static int
|
|
i830_lvds_mode_valid(xf86OutputPtr output, DisplayModePtr pMode)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
DisplayModePtr pFixedMode = pI830->panel_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)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(pScrn);
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
I830CrtcPrivatePtr intel_crtc = output->crtc->driver_private;
|
|
int i;
|
|
|
|
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 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 (pI830->panel_fixed_mode != NULL) {
|
|
adjusted_mode->HDisplay = pI830->panel_fixed_mode->HDisplay;
|
|
adjusted_mode->HSyncStart = pI830->panel_fixed_mode->HSyncStart;
|
|
adjusted_mode->HSyncEnd = pI830->panel_fixed_mode->HSyncEnd;
|
|
adjusted_mode->HTotal = pI830->panel_fixed_mode->HTotal;
|
|
adjusted_mode->VDisplay = pI830->panel_fixed_mode->VDisplay;
|
|
adjusted_mode->VSyncStart = pI830->panel_fixed_mode->VSyncStart;
|
|
adjusted_mode->VSyncEnd = pI830->panel_fixed_mode->VSyncEnd;
|
|
adjusted_mode->VTotal = pI830->panel_fixed_mode->VTotal;
|
|
adjusted_mode->Clock = pI830->panel_fixed_mode->Clock;
|
|
xf86SetModeCrtc(adjusted_mode, INTERLACE_HALVE_V);
|
|
}
|
|
|
|
/* XXX: if we don't have BIOS fixed timings (or we have
|
|
* a preferred mode from DDC, probably), we should use the
|
|
* DDC mode as the fixed timing.
|
|
*/
|
|
|
|
/* 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_mode_set(xf86OutputPtr output, DisplayModePtr mode,
|
|
DisplayModePtr adjusted_mode)
|
|
{
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
CARD32 pfit_control;
|
|
|
|
#if 0
|
|
/* The LVDS pin pair needs to be on before the DPLLs are enabled.
|
|
* This is an exception to the general rule that mode_set doesn't turn
|
|
* things on.
|
|
*/
|
|
OUTREG(LVDS, INREG(LVDS) | LVDS_PORT_EN | LVDS_PIPEB_SELECT);
|
|
#endif
|
|
|
|
/* Enable automatic panel scaling so that non-native modes fill the
|
|
* screen. Should be enabled before the pipe is enabled, according to
|
|
* register description and PRM.
|
|
*/
|
|
pfit_control = (PFIT_ENABLE |
|
|
VERT_AUTO_SCALE | HORIZ_AUTO_SCALE |
|
|
VERT_INTERP_BILINEAR | HORIZ_INTERP_BILINEAR);
|
|
|
|
if (pI830->panel_wants_dither)
|
|
pfit_control |= PANEL_8TO6_DITHER_ENABLE;
|
|
|
|
OUTREG(PFIT_CONTROL, 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)
|
|
{
|
|
I830OutputPrivatePtr intel_output = output->driver_private;
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
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->panel_fixed_mode != NULL)
|
|
return xf86DuplicateMode(pI830->panel_fixed_mode);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
i830_lvds_destroy (xf86OutputPtr output)
|
|
{
|
|
I830OutputPrivatePtr intel_output = output->driver_private;
|
|
|
|
if (intel_output)
|
|
xfree (intel_output);
|
|
}
|
|
|
|
#ifdef RANDR_12_INTERFACE
|
|
#define BACKLIGHT_NAME "BACKLIGHT"
|
|
static Atom backlight_atom;
|
|
#endif /* RANDR_12_INTERFACE */
|
|
|
|
static void
|
|
i830_lvds_create_resources(xf86OutputPtr output)
|
|
{
|
|
#ifdef RANDR_12_INTERFACE
|
|
ScrnInfoPtr pScrn = output->scrn;
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
INT32 range[2];
|
|
int data, err;
|
|
|
|
/* Set up the backlight property, which takes effect immediately
|
|
* and accepts values only within the range.
|
|
*
|
|
* XXX: Currently, RandR doesn't verify that properties set are
|
|
* within the range.
|
|
*/
|
|
backlight_atom = MakeAtom(BACKLIGHT_NAME, sizeof(BACKLIGHT_NAME) - 1,
|
|
TRUE);
|
|
|
|
range[0] = 0;
|
|
range[1] = i830_lvds_get_max_backlight(pScrn);
|
|
err = RRConfigureOutputProperty(output->randr_output, backlight_atom,
|
|
FALSE, TRUE, FALSE, 2, range);
|
|
if (err != 0) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"RRConfigureOutputProperty error, %d\n", err);
|
|
}
|
|
/* Set the current value of the backlight property */
|
|
data = pI830->backlight_duty_cycle;
|
|
err = RRChangeOutputProperty(output->randr_output, backlight_atom,
|
|
XA_INTEGER, 32, PropModeReplace, 4, &data,
|
|
FALSE);
|
|
if (err != 0) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"RRChangeOutputProperty error, %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);
|
|
|
|
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 > i830_lvds_get_max_backlight(pScrn))
|
|
return FALSE;
|
|
|
|
i830_lvds_set_backlight(pScrn, val);
|
|
pI830->backlight_duty_cycle = val;
|
|
return TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
#endif /* RANDR_12_INTERFACE */
|
|
|
|
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,
|
|
.mode_set = i830_lvds_mode_set,
|
|
.detect = i830_lvds_detect,
|
|
.get_modes = i830_lvds_get_modes,
|
|
#ifdef RANDR_12_INTERFACE
|
|
.set_property = i830_lvds_set_property,
|
|
#endif
|
|
.destroy = i830_lvds_destroy
|
|
};
|
|
|
|
void
|
|
i830_lvds_init(ScrnInfoPtr pScrn)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
xf86OutputPtr output;
|
|
I830OutputPrivatePtr intel_output;
|
|
DisplayModePtr modes, scan, bios_mode;
|
|
|
|
output = xf86OutputCreate (pScrn, &i830_lvds_output_funcs, "LVDS");
|
|
if (!output)
|
|
return;
|
|
intel_output = xnfcalloc (sizeof (I830OutputPrivateRec), 1);
|
|
if (!intel_output)
|
|
{
|
|
xf86OutputDestroy (output);
|
|
return;
|
|
}
|
|
intel_output->type = I830_OUTPUT_LVDS;
|
|
output->driver_private = intel_output;
|
|
output->subpixel_order = SubPixelHorizontalRGB;
|
|
output->interlaceAllowed = FALSE;
|
|
output->doubleScanAllowed = FALSE;
|
|
|
|
/* 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");
|
|
|
|
/* 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;
|
|
pI830->panel_fixed_mode = scan;
|
|
}
|
|
/* Delete the mode list */
|
|
while (modes != NULL)
|
|
xf86DeleteMode(&modes, modes);
|
|
|
|
/* If we didn't get EDID, try checking if the panel is already turned on.
|
|
* If so, assume that whatever is currently programmed is the correct mode.
|
|
*/
|
|
if (pI830->panel_fixed_mode == NULL) {
|
|
CARD32 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->panel_fixed_mode = i830_crtc_mode_get(pScrn, crtc);
|
|
if (pI830->panel_fixed_mode != NULL)
|
|
pI830->panel_fixed_mode->type |= M_T_PREFERRED;
|
|
}
|
|
}
|
|
|
|
/* 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.
|
|
*/
|
|
bios_mode = i830_bios_get_panel_mode(pScrn);
|
|
if (bios_mode != NULL) {
|
|
if (pI830->panel_fixed_mode != NULL) {
|
|
if (!xf86ModesEqual(pI830->panel_fixed_mode, bios_mode)) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
|
|
"BIOS panel mode data doesn't match probed data, "
|
|
"continuing with probed.\n");
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "BIOS mode:\n");
|
|
xf86PrintModeline(pScrn->scrnIndex, bios_mode);
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "probed mode:\n");
|
|
xf86PrintModeline(pScrn->scrnIndex, pI830->panel_fixed_mode);
|
|
xfree(bios_mode->name);
|
|
xfree(bios_mode);
|
|
}
|
|
} else {
|
|
pI830->panel_fixed_mode = bios_mode;
|
|
}
|
|
} else {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
|
|
"Couldn't detect panel mode. Disabling panel\n");
|
|
goto disable_exit;
|
|
}
|
|
|
|
/* Blacklist machines with BIOSes that list an LVDS panel without actually
|
|
* having one.
|
|
*/
|
|
if (pI830->PciInfo->chipType == PCI_CHIP_I945_GM) {
|
|
if (pI830->PciInfo->subsysVendor == 0xa0a0) /* aopen mini pc */
|
|
goto disable_exit;
|
|
|
|
if ((pI830->PciInfo->subsysVendor == 0x8086) &&
|
|
(pI830->PciInfo->subsysCard == 0x7270)) {
|
|
/* 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->panel_fixed_mode != NULL &&
|
|
pI830->panel_fixed_mode->HDisplay == 800 &&
|
|
pI830->panel_fixed_mode->VDisplay == 600)
|
|
{
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Suspected Mac Mini, ignoring the LVDS\n");
|
|
goto disable_exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
disable_exit:
|
|
xf86DestroyI2CBusRec(intel_output->pDDCBus, TRUE, TRUE);
|
|
xf86OutputDestroy(output);
|
|
}
|