xf86-video-intel/src/i830_lvds.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);
}