1114 lines
30 KiB
C
1114 lines
30 KiB
C
/* -*- c-basic-offset: 4 -*- */
|
|
/*
|
|
* 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 "xf86_ansic.h"
|
|
#include "i830.h"
|
|
#include "i830_bios.h"
|
|
#include "i830_display.h"
|
|
#include "i830_debug.h"
|
|
#include "i830_xf86Modes.h"
|
|
|
|
/** Returns the pixel clock for the given refclk and divisors. */
|
|
static int i830_clock(int refclk, int m1, int m2, int n, int p1, int p2)
|
|
{
|
|
return refclk * (5 * m1 + m2) / n / (p1 * p2);
|
|
}
|
|
|
|
static void
|
|
i830PrintPll(char *prefix, int refclk, int m1, int m2, int n, int p1, int p2)
|
|
{
|
|
int dotclock;
|
|
|
|
dotclock = i830_clock(refclk, m1, m2, n, p1, p2);
|
|
|
|
ErrorF("%s: dotclock %d ((%d, %d), %d, (%d, %d))\n", prefix, dotclock,
|
|
m1, m2, n, p1, p2);
|
|
}
|
|
|
|
/**
|
|
* Returns whether the given set of divisors are valid for a given refclk with
|
|
* the given outputs.
|
|
*
|
|
* The equation for these divisors would be:
|
|
* clk = refclk * (5 * m1 + m2) / n / (p1 * p2)
|
|
*/
|
|
static Bool
|
|
i830PllIsValid(ScrnInfoPtr pScrn, int outputs, int refclk, int m1, int m2,
|
|
int n, int p1, int p2)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
int p, m, vco, dotclock;
|
|
int min_m1, max_m1, min_m2, max_m2, min_m, max_m, min_n, max_n;
|
|
int min_p1, max_p1, min_p, max_p, min_vco, max_vco, min_dot, max_dot;
|
|
|
|
if (IS_I9XX(pI830)) {
|
|
min_m1 = 10;
|
|
max_m1 = 20;
|
|
min_m2 = 5;
|
|
max_m2 = 9;
|
|
min_m = 70;
|
|
max_m = 120;
|
|
min_n = 3;
|
|
max_n = 8;
|
|
min_p1 = 1;
|
|
max_p1 = 8;
|
|
if (outputs & PIPE_LCD_ACTIVE) {
|
|
min_p = 7;
|
|
max_p = 98;
|
|
} else {
|
|
min_p = 5;
|
|
max_p = 80;
|
|
}
|
|
min_vco = 1400000;
|
|
max_vco = 2800000;
|
|
min_dot = 20000;
|
|
max_dot = 400000;
|
|
} else {
|
|
min_m1 = 18;
|
|
max_m1 = 26;
|
|
min_m2 = 6;
|
|
max_m2 = 16;
|
|
min_m = 96;
|
|
max_m = 140;
|
|
min_n = 3;
|
|
max_n = 16;
|
|
min_p1 = 2;
|
|
max_p1 = 18;
|
|
min_vco = 930000;
|
|
max_vco = 1400000;
|
|
min_dot = 20000;
|
|
max_dot = 350000;
|
|
min_p = 4;
|
|
max_p = 128;
|
|
}
|
|
|
|
p = p1 * p2;
|
|
m = 5 * m1 + m2;
|
|
vco = refclk * m / n;
|
|
dotclock = i830_clock(refclk, m1, m2, n, p1, p2);
|
|
|
|
if (p1 < min_p1 || p1 > max_p1)
|
|
return FALSE;
|
|
if (p < min_p || p > max_p)
|
|
return FALSE;
|
|
if (m2 < min_m2 || m2 > max_m2)
|
|
return FALSE;
|
|
if (m1 < min_m1 || m1 > max_m1)
|
|
return FALSE;
|
|
if (m1 <= m2)
|
|
return FALSE;
|
|
if (m < min_m || m > max_m)
|
|
return FALSE;
|
|
if (n < min_n || n > max_n)
|
|
return FALSE;
|
|
if (vco < min_vco || vco > max_vco)
|
|
return FALSE;
|
|
/* XXX: We may need to be checking "Dot clock" depending on the multiplier,
|
|
* output, etc., rather than just a single range.
|
|
*/
|
|
if (dotclock < min_dot || dotclock > max_dot)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Returns a set of divisors for the desired target clock with the given refclk,
|
|
* or FALSE. Divisor values are the actual divisors for
|
|
* clk = refclk * (5 * m1 + m2) / n / (p1 * p2)
|
|
*/
|
|
static Bool
|
|
i830FindBestPLL(ScrnInfoPtr pScrn, int outputs, int target, int refclk,
|
|
int *outm1, int *outm2, int *outn, int *outp1, int *outp2)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
int m1, m2, n, p1, p2;
|
|
int err = target;
|
|
int min_m1, max_m1, min_m2, max_m2, min_n, max_n, min_p1, max_p1;
|
|
|
|
if (IS_I9XX(pI830)) {
|
|
min_m1 = 10;
|
|
max_m1 = 20;
|
|
min_m2 = 5;
|
|
max_m2 = 9;
|
|
min_n = 3;
|
|
max_n = 8;
|
|
min_p1 = 1;
|
|
max_p1 = 8;
|
|
if (outputs & PIPE_LCD_ACTIVE) {
|
|
if (target < 200000) /* XXX: Is this the right cutoff? */
|
|
p2 = 14;
|
|
else
|
|
p2 = 7;
|
|
} else {
|
|
if (target < 200000)
|
|
p2 = 10;
|
|
else
|
|
p2 = 5;
|
|
}
|
|
} else {
|
|
min_m1 = 18;
|
|
max_m1 = 26;
|
|
min_m2 = 6;
|
|
max_m2 = 16;
|
|
min_n = 3;
|
|
max_n = 16;
|
|
min_p1 = 2;
|
|
max_p1 = 18;
|
|
if (target < 165000)
|
|
p2 = 4;
|
|
else
|
|
p2 = 2;
|
|
}
|
|
|
|
|
|
for (m1 = min_m1; m1 <= max_m1; m1++) {
|
|
for (m2 = min_m2; m2 < max_m2; m2++) {
|
|
for (n = min_n; n <= max_n; n++) {
|
|
for (p1 = min_p1; p1 <= max_p1; p1++) {
|
|
int clock, this_err;
|
|
|
|
if (!i830PllIsValid(pScrn, outputs, refclk, m1, m2, n,
|
|
p1, p2)) {
|
|
continue;
|
|
}
|
|
|
|
clock = i830_clock(refclk, m1, m2, n, p1, p2);
|
|
this_err = abs(clock - target);
|
|
if (this_err < err) {
|
|
*outm1 = m1;
|
|
*outm2 = m2;
|
|
*outn = n;
|
|
*outp1 = p1;
|
|
*outp2 = p2;
|
|
err = this_err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return (err != target);
|
|
}
|
|
|
|
static void
|
|
i830WaitForVblank(ScrnInfoPtr pScreen)
|
|
{
|
|
/* Wait for 20ms, i.e. one cycle at 50hz. */
|
|
usleep(20000);
|
|
}
|
|
|
|
void
|
|
i830PipeSetBase(ScrnInfoPtr pScrn, int pipe, int x, int y)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
unsigned long Start;
|
|
|
|
if (I830IsPrimary(pScrn))
|
|
Start = pI830->FrontBuffer.Start;
|
|
else {
|
|
I830Ptr pI8301 = I830PTR(pI830->entityPrivate->pScrn_1);
|
|
Start = pI8301->FrontBuffer2.Start;
|
|
}
|
|
|
|
if (pipe == 0)
|
|
OUTREG(DSPABASE, Start + ((y * pScrn->displayWidth + x) * pI830->cpp));
|
|
else
|
|
OUTREG(DSPBBASE, Start + ((y * pScrn->displayWidth + x) * pI830->cpp));
|
|
}
|
|
|
|
/**
|
|
* Sets the given video mode on the given pipe. Assumes that plane A feeds
|
|
* pipe A, and plane B feeds pipe B. Should not affect the other planes/pipes.
|
|
*/
|
|
static Bool
|
|
i830PipeSetMode(ScrnInfoPtr pScrn, DisplayModePtr pMode, int pipe)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
int m1 = 0, m2 = 0, n = 0, p1 = 0, p2 = 0;
|
|
CARD32 dpll = 0, fp = 0, temp;
|
|
CARD32 htot, hblank, hsync, vtot, vblank, vsync, dspcntr;
|
|
CARD32 pipesrc, dspsize, adpa;
|
|
CARD32 sdvob = 0, sdvoc = 0, dvo = 0;
|
|
Bool ok, is_sdvo, is_dvo;
|
|
int refclk, pixel_clock, sdvo_pixel_multiply;
|
|
int outputs;
|
|
DisplayModePtr pMasterMode = pMode;
|
|
|
|
assert(pMode->VRefresh != 0.0);
|
|
/* If we've got a list of modes probed for the device, find the best match
|
|
* in there to the requested mode.
|
|
*/
|
|
if (pI830->pipeMon[pipe] != NULL) {
|
|
DisplayModePtr pBest = NULL, pScan;
|
|
|
|
assert(pScan->VRefresh != 0.0);
|
|
for (pScan = pI830->pipeMon[pipe]->Modes; pScan != NULL;
|
|
pScan = pScan->next)
|
|
{
|
|
/* If there's an exact match, we're done. */
|
|
if (I830ModesEqual(pScan, pMode)) {
|
|
pBest = pMode;
|
|
break;
|
|
}
|
|
|
|
/* Reject if it's larger than the desired mode. */
|
|
if (pScan->HDisplay > pMode->HDisplay ||
|
|
pScan->VDisplay > pMode->VDisplay)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pBest == NULL) {
|
|
pBest = pScan;
|
|
continue;
|
|
}
|
|
/* Find if it's closer to the right size than the current best
|
|
* option.
|
|
*/
|
|
if ((pScan->HDisplay > pBest->HDisplay &&
|
|
pScan->VDisplay >= pBest->VDisplay) ||
|
|
(pScan->HDisplay >= pBest->HDisplay &&
|
|
pScan->VDisplay > pBest->VDisplay))
|
|
{
|
|
pBest = pScan;
|
|
continue;
|
|
}
|
|
/* Find if it's still closer to the right refresh than the current
|
|
* best resolution.
|
|
*/
|
|
if (pScan->HDisplay == pBest->HDisplay &&
|
|
pScan->VDisplay == pBest->VDisplay &&
|
|
(fabs(pScan->VRefresh - pMode->VRefresh) <
|
|
fabs(pBest->VRefresh - pMode->VRefresh)))
|
|
{
|
|
pBest = pScan;
|
|
}
|
|
}
|
|
if (pBest != NULL) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"Choosing pipe %d's mode %dx%d@%.1f instead of xf86 "
|
|
"mode %dx%d@%.1f\n", pipe,
|
|
pBest->HDisplay, pBest->VDisplay, pBest->VRefresh,
|
|
pMode->HDisplay, pMode->VDisplay, pMode->VRefresh);
|
|
pMode = pBest;
|
|
}
|
|
}
|
|
if (pipe == 0)
|
|
outputs = pI830->operatingDevices & 0xff;
|
|
else
|
|
outputs = (pI830->operatingDevices >> 8) & 0xff;
|
|
|
|
if (outputs & PIPE_LCD_ACTIVE) {
|
|
if (I830ModesEqual(&pI830->pipeCurMode[pipe], pMasterMode))
|
|
return TRUE;
|
|
} else {
|
|
if (I830ModesEqual(&pI830->pipeCurMode[pipe], pMode))
|
|
return TRUE;
|
|
}
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Requested pix clock: %d\n",
|
|
pMode->Clock);
|
|
|
|
if ((outputs & PIPE_LCD_ACTIVE) && (outputs & ~PIPE_LCD_ACTIVE)) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"Can't enable LVDS and non-LVDS on the same pipe\n");
|
|
return FALSE;
|
|
}
|
|
if (((outputs & PIPE_TV_ACTIVE) && (outputs & ~PIPE_TV_ACTIVE)) ||
|
|
((outputs & PIPE_TV2_ACTIVE) && (outputs & ~PIPE_TV2_ACTIVE))) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"Can't enable a TV and any other output on the same pipe\n");
|
|
return FALSE;
|
|
}
|
|
if (pipe == 0 && (outputs & PIPE_LCD_ACTIVE)) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"Can't support LVDS on pipe A\n");
|
|
return FALSE;
|
|
}
|
|
if ((outputs & PIPE_DFP_ACTIVE) || (outputs & PIPE_DFP2_ACTIVE)) {
|
|
/* We'll change how we control outputs soon, but to get the SDVO code up
|
|
* and running, just check for these two possibilities.
|
|
*/
|
|
if (IS_I9XX(pI830)) {
|
|
is_sdvo = TRUE;
|
|
is_dvo = FALSE;
|
|
} else {
|
|
is_dvo = TRUE;
|
|
is_sdvo = FALSE;
|
|
}
|
|
} else {
|
|
is_sdvo = FALSE;
|
|
is_dvo = FALSE;
|
|
}
|
|
|
|
htot = (pMode->CrtcHDisplay - 1) | ((pMode->CrtcHTotal - 1) << 16);
|
|
hblank = (pMode->CrtcHBlankStart - 1) | ((pMode->CrtcHBlankEnd - 1) << 16);
|
|
hsync = (pMode->CrtcHSyncStart - 1) | ((pMode->CrtcHSyncEnd - 1) << 16);
|
|
vtot = (pMode->CrtcVDisplay - 1) | ((pMode->CrtcVTotal - 1) << 16);
|
|
vblank = (pMode->CrtcVBlankStart - 1) | ((pMode->CrtcVBlankEnd - 1) << 16);
|
|
vsync = (pMode->CrtcVSyncStart - 1) | ((pMode->CrtcVSyncEnd - 1) << 16);
|
|
pipesrc = ((pMode->HDisplay - 1) << 16) | (pMode->VDisplay - 1);
|
|
dspsize = ((pMode->VDisplay - 1) << 16) | (pMode->HDisplay - 1);
|
|
pixel_clock = pMode->Clock;
|
|
if (outputs & PIPE_LCD_ACTIVE && pI830->panel_fixed_hactive != 0)
|
|
{
|
|
/* To enable panel fitting, we need to set the pipe timings to that of
|
|
* the screen at its full resolution. So, drop the timings from the
|
|
* BIOS VBT tables here.
|
|
*/
|
|
htot = (pI830->panel_fixed_hactive - 1) |
|
|
((pI830->panel_fixed_hactive + pI830->panel_fixed_hblank - 1)
|
|
<< 16);
|
|
hblank = (pI830->panel_fixed_hactive - 1) |
|
|
((pI830->panel_fixed_hactive + pI830->panel_fixed_hblank - 1)
|
|
<< 16);
|
|
hsync = (pI830->panel_fixed_hactive + pI830->panel_fixed_hsyncoff - 1) |
|
|
((pI830->panel_fixed_hactive + pI830->panel_fixed_hsyncoff +
|
|
pI830->panel_fixed_hsyncwidth - 1) << 16);
|
|
|
|
vtot = (pI830->panel_fixed_vactive - 1) |
|
|
((pI830->panel_fixed_vactive + pI830->panel_fixed_vblank - 1)
|
|
<< 16);
|
|
vblank = (pI830->panel_fixed_vactive - 1) |
|
|
((pI830->panel_fixed_vactive + pI830->panel_fixed_vblank - 1)
|
|
<< 16);
|
|
vsync = (pI830->panel_fixed_vactive + pI830->panel_fixed_vsyncoff - 1) |
|
|
((pI830->panel_fixed_vactive + pI830->panel_fixed_vsyncoff +
|
|
pI830->panel_fixed_vsyncwidth - 1) << 16);
|
|
pixel_clock = pI830->panel_fixed_clock;
|
|
|
|
if (pMasterMode->HDisplay <= pI830->panel_fixed_hactive &&
|
|
pMasterMode->HDisplay <= pI830->panel_fixed_vactive)
|
|
{
|
|
pipesrc = ((pMasterMode->HDisplay - 1) << 16) |
|
|
(pMasterMode->VDisplay - 1);
|
|
dspsize = ((pMasterMode->VDisplay - 1) << 16) |
|
|
(pMasterMode->HDisplay - 1);
|
|
}
|
|
}
|
|
|
|
if (pMode->Clock >= 100000)
|
|
sdvo_pixel_multiply = 1;
|
|
else if (pMode->Clock >= 50000)
|
|
sdvo_pixel_multiply = 2;
|
|
else
|
|
sdvo_pixel_multiply = 4;
|
|
|
|
/* In SDVO, we need to keep the clock on the bus between 1Ghz and 2Ghz.
|
|
* The clock on the bus is 10 times the pixel clock normally. If that
|
|
* would be too low, we run the DPLL at a multiple of the pixel clock, and
|
|
* tell the SDVO device the multiplier so it can throw away the dummy bytes.
|
|
*/
|
|
if (is_sdvo) {
|
|
pixel_clock *= sdvo_pixel_multiply;
|
|
}
|
|
|
|
if (IS_I9XX(pI830)) {
|
|
refclk = 96000;
|
|
} else {
|
|
refclk = 48000;
|
|
}
|
|
ok = i830FindBestPLL(pScrn, outputs, pixel_clock, refclk, &m1, &m2, &n,
|
|
&p1, &p2);
|
|
if (!ok) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
|
|
"Couldn't find PLL settings for mode!\n");
|
|
return FALSE;
|
|
}
|
|
|
|
dpll = DPLL_VCO_ENABLE | DPLL_VGA_MODE_DIS;
|
|
if (IS_I9XX(pI830)) {
|
|
if (outputs & PIPE_LCD_ACTIVE)
|
|
dpll |= DPLLB_MODE_LVDS;
|
|
else
|
|
dpll |= DPLLB_MODE_DAC_SERIAL;
|
|
dpll |= (1 << (p1 - 1)) << 16;
|
|
switch (p2) {
|
|
case 5:
|
|
dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_5;
|
|
break;
|
|
case 7:
|
|
dpll |= DPLLB_LVDS_P2_CLOCK_DIV_7;
|
|
break;
|
|
case 10:
|
|
dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_10;
|
|
break;
|
|
case 14:
|
|
dpll |= DPLLB_LVDS_P2_CLOCK_DIV_14;
|
|
break;
|
|
}
|
|
} else {
|
|
dpll |= (p1 - 2) << 16;
|
|
if (p2 == 4)
|
|
dpll |= PLL_P2_DIVIDE_BY_4;
|
|
}
|
|
|
|
if (outputs & (PIPE_TV_ACTIVE | PIPE_TV2_ACTIVE))
|
|
dpll |= PLL_REF_INPUT_TVCLKINBC;
|
|
else
|
|
dpll |= PLL_REF_INPUT_DREFCLK;
|
|
|
|
if (is_dvo) {
|
|
dpll |= DPLL_DVO_HIGH_SPEED;
|
|
|
|
/* Save the data order, since I don't know what it should be set to. */
|
|
dvo = INREG(DVOC) & (DVO_PRESERVE_MASK | DVO_DATA_ORDER_GBRG);
|
|
dvo |= DVO_ENABLE;
|
|
dvo |= DVO_DATA_ORDER_FP | DVO_BORDER_ENABLE | DVO_BLANK_ACTIVE_HIGH;
|
|
|
|
if (pipe == 1)
|
|
dvo |= DVO_PIPE_B_SELECT;
|
|
|
|
if (pMode->Flags & V_PHSYNC)
|
|
dvo |= DVO_HSYNC_ACTIVE_HIGH;
|
|
if (pMode->Flags & V_PVSYNC)
|
|
dvo |= DVO_VSYNC_ACTIVE_HIGH;
|
|
|
|
OUTREG(DVOC, dvo & ~DVO_ENABLE);
|
|
}
|
|
|
|
if (is_sdvo) {
|
|
dpll |= DPLL_DVO_HIGH_SPEED;
|
|
|
|
ErrorF("DVOB: %08x\nDVOC: %08x\n", (int)INREG(SDVOB), (int)INREG(SDVOC));
|
|
|
|
sdvob = INREG(SDVOB) & SDVOB_PRESERVE_MASK;
|
|
sdvoc = INREG(SDVOC) & SDVOC_PRESERVE_MASK;
|
|
sdvob |= SDVO_ENABLE | (9 << 19) | SDVO_BORDER_ENABLE;
|
|
sdvoc |= 9 << 19;
|
|
if (pipe == 1)
|
|
sdvob |= SDVO_PIPE_B_SELECT;
|
|
|
|
if (IS_I945G(pI830) || IS_I945GM(pI830))
|
|
dpll |= (sdvo_pixel_multiply - 1) << SDVO_MULTIPLIER_SHIFT_HIRES;
|
|
else
|
|
sdvob |= (sdvo_pixel_multiply - 1) << SDVO_PORT_MULTIPLY_SHIFT;
|
|
|
|
OUTREG(SDVOC, INREG(SDVOC) & ~SDVO_ENABLE);
|
|
OUTREG(SDVOB, INREG(SDVOB) & ~SDVO_ENABLE);
|
|
}
|
|
|
|
fp = ((n - 2) << 16) | ((m1 - 2) << 8) | (m2 - 2);
|
|
|
|
#if 1
|
|
ErrorF("hact: %d htot: %d hbstart: %d hbend: %d hsyncstart: %d hsyncend: %d\n",
|
|
(int)(htot & 0xffff) + 1, (int)(htot >> 16) + 1,
|
|
(int)(hblank & 0xffff) + 1, (int)(hblank >> 16) + 1,
|
|
(int)(hsync & 0xffff) + 1, (int)(hsync >> 16) + 1);
|
|
ErrorF("vact: %d vtot: %d vbstart: %d vbend: %d vsyncstart: %d vsyncend: %d\n",
|
|
(int)(vtot & 0xffff) + 1, (int)(vtot >> 16) + 1,
|
|
(int)(vblank & 0xffff) + 1, (int)(vblank >> 16) + 1,
|
|
(int)(vsync & 0xffff) + 1, (int)(vsync >> 16) + 1);
|
|
ErrorF("pipesrc: %dx%d, dspsize: %dx%d\n",
|
|
(int)(pipesrc >> 16) + 1, (int)(pipesrc & 0xffff) + 1,
|
|
(int)(dspsize & 0xffff) + 1, (int)(dspsize >> 16) + 1);
|
|
#endif
|
|
|
|
i830PrintPll("chosen", refclk, m1, m2, n, p1, p2);
|
|
ErrorF("clock regs: 0x%08x, 0x%08x\n", (int)dpll, (int)fp);
|
|
|
|
dspcntr = DISPLAY_PLANE_ENABLE;
|
|
switch (pScrn->bitsPerPixel) {
|
|
case 8:
|
|
dspcntr |= DISPPLANE_8BPP | DISPPLANE_GAMMA_ENABLE;
|
|
break;
|
|
case 16:
|
|
if (pScrn->depth == 15)
|
|
dspcntr |= DISPPLANE_15_16BPP;
|
|
else
|
|
dspcntr |= DISPPLANE_16BPP;
|
|
break;
|
|
case 32:
|
|
dspcntr |= DISPPLANE_32BPP_NO_ALPHA;
|
|
break;
|
|
default:
|
|
FatalError("unknown display bpp\n");
|
|
}
|
|
|
|
if (is_sdvo)
|
|
adpa = ADPA_DAC_DISABLE;
|
|
else
|
|
adpa = ADPA_DAC_ENABLE;
|
|
if (pMode->Flags & V_PHSYNC)
|
|
adpa |= ADPA_HSYNC_ACTIVE_HIGH;
|
|
if (pMode->Flags & V_PVSYNC)
|
|
adpa |= ADPA_VSYNC_ACTIVE_HIGH;
|
|
|
|
if (pipe == 0) {
|
|
dspcntr |= DISPPLANE_SEL_PIPE_A;
|
|
adpa |= ADPA_PIPE_A_SELECT;
|
|
} else {
|
|
dspcntr |= DISPPLANE_SEL_PIPE_B;
|
|
adpa |= ADPA_PIPE_B_SELECT;
|
|
}
|
|
|
|
OUTREG(VGACNTRL, VGA_DISP_DISABLE);
|
|
|
|
/* Set up display timings and PLLs for the pipe. */
|
|
if (pipe == 0) {
|
|
/* First, disable display planes */
|
|
temp = INREG(DSPACNTR);
|
|
OUTREG(DSPACNTR, temp & ~DISPLAY_PLANE_ENABLE);
|
|
|
|
/* Wait for vblank for the disable to take effect */
|
|
i830WaitForVblank(pScrn);
|
|
|
|
/* Next, disable display pipes */
|
|
temp = INREG(PIPEACONF);
|
|
OUTREG(PIPEACONF, temp & ~PIPEACONF_ENABLE);
|
|
|
|
OUTREG(FPA0, fp);
|
|
OUTREG(DPLL_A, dpll);
|
|
|
|
OUTREG(HTOTAL_A, htot);
|
|
OUTREG(HBLANK_A, hblank);
|
|
OUTREG(HSYNC_A, hsync);
|
|
OUTREG(VTOTAL_A, vtot);
|
|
OUTREG(VBLANK_A, vblank);
|
|
OUTREG(VSYNC_A, vsync);
|
|
OUTREG(DSPASTRIDE, pScrn->displayWidth * pI830->cpp);
|
|
OUTREG(DSPASIZE, dspsize);
|
|
OUTREG(DSPAPOS, 0);
|
|
i830PipeSetBase(pScrn, pipe, pScrn->frameX0, pScrn->frameY0);
|
|
OUTREG(PIPEASRC, pipesrc);
|
|
|
|
/* Then, turn the pipe on first */
|
|
temp = INREG(PIPEACONF);
|
|
OUTREG(PIPEACONF, temp | PIPEACONF_ENABLE);
|
|
|
|
/* And then turn the plane on */
|
|
OUTREG(DSPACNTR, dspcntr);
|
|
} else {
|
|
/* Always make sure the LVDS is off before we play with DPLLs and pipe
|
|
* configuration.
|
|
*/
|
|
i830SetLVDSPanelPower(pScrn, FALSE);
|
|
|
|
/* First, disable display planes */
|
|
temp = INREG(DSPBCNTR);
|
|
OUTREG(DSPBCNTR, temp & ~DISPLAY_PLANE_ENABLE);
|
|
|
|
/* Wait for vblank for the disable to take effect */
|
|
i830WaitForVblank(pScrn);
|
|
|
|
/* Next, disable display pipes */
|
|
temp = INREG(PIPEBCONF);
|
|
OUTREG(PIPEBCONF, temp & ~PIPEBCONF_ENABLE);
|
|
|
|
if (outputs & PIPE_LCD_ACTIVE) {
|
|
/* Disable the PLL before messing with LVDS enable */
|
|
OUTREG(FPB0, fp & ~DPLL_VCO_ENABLE);
|
|
|
|
/* LVDS must be powered on before PLL is enabled and before power
|
|
* sequencing the panel.
|
|
*/
|
|
temp = INREG(LVDS);
|
|
OUTREG(LVDS, temp | LVDS_PORT_EN | LVDS_PIPEB_SELECT);
|
|
}
|
|
|
|
OUTREG(FPB0, fp);
|
|
OUTREG(DPLL_B, dpll);
|
|
OUTREG(HTOTAL_B, htot);
|
|
OUTREG(HBLANK_B, hblank);
|
|
OUTREG(HSYNC_B, hsync);
|
|
OUTREG(VTOTAL_B, vtot);
|
|
OUTREG(VBLANK_B, vblank);
|
|
OUTREG(VSYNC_B, vsync);
|
|
OUTREG(DSPBSTRIDE, pScrn->displayWidth * pI830->cpp);
|
|
OUTREG(DSPBSIZE, dspsize);
|
|
OUTREG(DSPBPOS, 0);
|
|
i830PipeSetBase(pScrn, pipe, pScrn->frameX0, pScrn->frameY0);
|
|
OUTREG(PIPEBSRC, pipesrc);
|
|
|
|
if (outputs & PIPE_LCD_ACTIVE) {
|
|
CARD32 pfit_control;
|
|
|
|
/* Enable automatic panel scaling so that non-native modes fill the
|
|
* screen.
|
|
*/
|
|
/* XXX: Allow (auto-?) enabling of 8-to-6 dithering */
|
|
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);
|
|
}
|
|
|
|
/* Then, turn the pipe on first */
|
|
temp = INREG(PIPEBCONF);
|
|
OUTREG(PIPEBCONF, temp | PIPEBCONF_ENABLE);
|
|
|
|
/* And then turn the plane on */
|
|
OUTREG(DSPBCNTR, dspcntr);
|
|
|
|
if (outputs & PIPE_LCD_ACTIVE) {
|
|
i830SetLVDSPanelPower(pScrn, TRUE);
|
|
}
|
|
}
|
|
|
|
if (outputs & PIPE_CRT_ACTIVE)
|
|
OUTREG(ADPA, adpa);
|
|
|
|
if (is_dvo) {
|
|
/*OUTREG(DVOB_SRCDIM, (pMode->HDisplay << DVO_SRCDIM_HORIZONTAL_SHIFT) |
|
|
(pMode->VDisplay << DVO_SRCDIM_VERTICAL_SHIFT));*/
|
|
OUTREG(DVOC_SRCDIM, (pMode->HDisplay << DVO_SRCDIM_HORIZONTAL_SHIFT) |
|
|
(pMode->VDisplay << DVO_SRCDIM_VERTICAL_SHIFT));
|
|
/*OUTREG(DVOB, dvo);*/
|
|
OUTREG(DVOC, dvo);
|
|
}
|
|
|
|
if (is_sdvo) {
|
|
OUTREG(SDVOB, sdvob);
|
|
OUTREG(SDVOC, sdvoc);
|
|
}
|
|
|
|
if (outputs & PIPE_LCD_ACTIVE) {
|
|
pI830->pipeCurMode[pipe] = *pMasterMode;
|
|
} else {
|
|
pI830->pipeCurMode[pipe] = *pMode;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
i830DisableUnusedFunctions(ScrnInfoPtr pScrn)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
int outputsA, outputsB;
|
|
|
|
outputsA = pI830->operatingDevices & 0xff;
|
|
outputsB = (pI830->operatingDevices >> 8) & 0xff;
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling unused functions\n");
|
|
|
|
/* First, disable the unused outputs */
|
|
if ((outputsA & PIPE_CRT_ACTIVE) == 0 &&
|
|
(outputsB & PIPE_CRT_ACTIVE) == 0)
|
|
{
|
|
CARD32 adpa = INREG(ADPA);
|
|
|
|
if (adpa & ADPA_DAC_ENABLE) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling CRT output\n");
|
|
OUTREG(ADPA, adpa & ~ADPA_DAC_ENABLE);
|
|
}
|
|
}
|
|
|
|
if ((outputsB & PIPE_LCD_ACTIVE) == 0) {
|
|
CARD32 pp_status = INREG(PP_STATUS);
|
|
|
|
if (pp_status & PP_ON) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling LVDS output\n");
|
|
i830SetLVDSPanelPower(pScrn, FALSE);
|
|
}
|
|
}
|
|
|
|
if (IS_I9XX(pI830) && ((outputsA & PIPE_DFP_ACTIVE) == 0 &&
|
|
(outputsB & PIPE_DFP_ACTIVE) == 0))
|
|
{
|
|
CARD32 sdvob = INREG(SDVOB);
|
|
CARD32 sdvoc = INREG(SDVOC);
|
|
|
|
if (sdvob & SDVO_ENABLE) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling SDVOB output\n");
|
|
OUTREG(SDVOB, sdvob & ~SDVO_ENABLE);
|
|
}
|
|
if (sdvoc & SDVO_ENABLE) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling SDVOC output\n");
|
|
OUTREG(SDVOC, sdvoc & ~SDVO_ENABLE);
|
|
}
|
|
}
|
|
|
|
/* Now, any unused plane, pipe, and DPLL (FIXME: except for DVO, i915
|
|
* internal TV) should have no outputs trying to pull data out of it, so
|
|
* we're ready to turn those off.
|
|
*/
|
|
if (!pI830->planeEnabled[0]) {
|
|
CARD32 dspcntr, pipeconf, dpll;
|
|
|
|
dspcntr = INREG(DSPACNTR);
|
|
if (dspcntr & DISPLAY_PLANE_ENABLE) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling plane A\n");
|
|
OUTREG(DSPACNTR, dspcntr & ~DISPLAY_PLANE_ENABLE);
|
|
|
|
/* Wait for vblank for the disable to take effect */
|
|
i830WaitForVblank(pScrn);
|
|
}
|
|
|
|
pipeconf = INREG(PIPEACONF);
|
|
if (pipeconf & PIPEACONF_ENABLE) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling pipe A\n");
|
|
OUTREG(PIPEACONF, pipeconf & ~PIPEACONF_ENABLE);
|
|
}
|
|
|
|
dpll = INREG(DPLL_A);
|
|
if (dpll & DPLL_VCO_ENABLE) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling DPLL A\n");
|
|
OUTREG(DPLL_A, dpll & ~DPLL_VCO_ENABLE);
|
|
}
|
|
|
|
memset(&pI830->pipeCurMode[0], 0, sizeof(pI830->pipeCurMode[0]));
|
|
}
|
|
|
|
if (!pI830->planeEnabled[1]) {
|
|
CARD32 dspcntr, pipeconf, dpll;
|
|
|
|
dspcntr = INREG(DSPBCNTR);
|
|
if (dspcntr & DISPLAY_PLANE_ENABLE) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling plane B\n");
|
|
OUTREG(DSPBCNTR, dspcntr & ~DISPLAY_PLANE_ENABLE);
|
|
|
|
/* Wait for vblank for the disable to take effect */
|
|
i830WaitForVblank(pScrn);
|
|
}
|
|
|
|
pipeconf = INREG(PIPEBCONF);
|
|
if (pipeconf & PIPEBCONF_ENABLE) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling pipe B\n");
|
|
OUTREG(PIPEBCONF, pipeconf & ~PIPEBCONF_ENABLE);
|
|
}
|
|
|
|
dpll = INREG(DPLL_B);
|
|
if (dpll & DPLL_VCO_ENABLE) {
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Disabling DPLL B\n");
|
|
OUTREG(DPLL_B, dpll & ~DPLL_VCO_ENABLE);
|
|
}
|
|
|
|
memset(&pI830->pipeCurMode[1], 0, sizeof(pI830->pipeCurMode[1]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function sets the given mode on the active pipes.
|
|
*/
|
|
Bool
|
|
i830SetMode(ScrnInfoPtr pScrn, DisplayModePtr pMode)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
Bool ok = TRUE;
|
|
CARD32 planeA, planeB;
|
|
#ifdef XF86DRI
|
|
Bool didLock = FALSE;
|
|
#endif
|
|
int i;
|
|
|
|
DPRINTF(PFX, "i830SetMode\n");
|
|
|
|
#ifdef XF86DRI
|
|
didLock = I830DRILock(pScrn);
|
|
#endif
|
|
|
|
if (pI830->operatingDevices & 0xff) {
|
|
pI830->planeEnabled[0] = 1;
|
|
} else {
|
|
pI830->planeEnabled[0] = 0;
|
|
}
|
|
|
|
if (pI830->operatingDevices & 0xff00) {
|
|
pI830->planeEnabled[1] = 1;
|
|
} else {
|
|
pI830->planeEnabled[1] = 0;
|
|
}
|
|
|
|
for (i = 0; i < pI830->num_outputs; i++) {
|
|
struct _I830OutputRec *output = &pI830->output[i];
|
|
|
|
if (output->sdvo_drv)
|
|
I830SDVOPreSetMode(output->sdvo_drv, pMode);
|
|
|
|
if (output->i2c_drv != NULL)
|
|
output->i2c_drv->vid_rec->Mode(output->i2c_drv->dev_priv,
|
|
pMode);
|
|
}
|
|
|
|
if (pI830->planeEnabled[0]) {
|
|
ok = i830PipeSetMode(pScrn, pMode, 0);
|
|
if (!ok)
|
|
goto done;
|
|
}
|
|
if (pI830->planeEnabled[1]) {
|
|
ok = i830PipeSetMode(pScrn, pMode, 1);
|
|
if (!ok)
|
|
goto done;
|
|
}
|
|
for (i = 0; i < pI830->num_outputs; i++) {
|
|
if (pI830->output[i].sdvo_drv)
|
|
I830SDVOPostSetMode(pI830->output[i].sdvo_drv, pMode);
|
|
}
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Mode bandwidth is %d Mpixel/s\n",
|
|
(int)(pMode->HDisplay * pMode->VDisplay *
|
|
pMode->VRefresh / 1000000));
|
|
|
|
if (pI830->savedCurrentMode) {
|
|
/* We're done with the currentMode that the last randr probe had left
|
|
* behind, so free it.
|
|
*/
|
|
xfree(pI830->savedCurrentMode->name);
|
|
xfree(pI830->savedCurrentMode);
|
|
pI830->savedCurrentMode = NULL;
|
|
|
|
/* If we might have enabled/disabled some pipes, we need to reset
|
|
* cloning mode support.
|
|
*/
|
|
if ((pI830->operatingDevices & 0x00ff) &&
|
|
(pI830->operatingDevices & 0xff00))
|
|
{
|
|
pI830->Clone = TRUE;
|
|
} else {
|
|
pI830->Clone = FALSE;
|
|
}
|
|
|
|
/* If HW cursor currently showing, reset cursor state */
|
|
if (pI830->CursorInfoRec && !pI830->SWCursor && pI830->cursorOn)
|
|
pI830->CursorInfoRec->ShowCursor(pScrn);
|
|
}
|
|
|
|
i830DisableUnusedFunctions(pScrn);
|
|
|
|
planeA = INREG(DSPACNTR);
|
|
planeB = INREG(DSPBCNTR);
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Display plane A is now %s and connected to %s.\n",
|
|
pI830->planeEnabled[0] ? "enabled" : "disabled",
|
|
planeA & DISPPLANE_SEL_PIPE_MASK ? "Pipe B" : "Pipe A");
|
|
if (pI830->availablePipes == 2)
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO,
|
|
"Display plane B is now %s and connected to %s.\n",
|
|
pI830->planeEnabled[1] ? "enabled" : "disabled",
|
|
planeB & DISPPLANE_SEL_PIPE_MASK ? "Pipe B" : "Pipe A");
|
|
|
|
#ifdef XF86DRI
|
|
I830DRISetVBlankInterrupt (pScrn, TRUE);
|
|
#endif
|
|
done:
|
|
#ifdef XF86DRI
|
|
if (didLock)
|
|
I830DRIUnlock(pScrn);
|
|
#endif
|
|
|
|
i830DumpRegs (pScrn);
|
|
I830DumpSDVO (pScrn);
|
|
return ok;
|
|
}
|
|
|
|
/**
|
|
* Uses CRT_HOTPLUG_EN and CRT_HOTPLUG_STAT to detect CRT presence.
|
|
*
|
|
* Only for I945G/GM.
|
|
*/
|
|
static Bool
|
|
i830HotplugDetectCRT(ScrnInfoPtr pScrn)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
CARD32 temp;
|
|
const int timeout_ms = 1000;
|
|
int starttime, curtime;
|
|
|
|
temp = INREG(PORT_HOTPLUG_EN);
|
|
|
|
OUTREG(PORT_HOTPLUG_EN, temp | CRT_HOTPLUG_FORCE_DETECT | (1 << 5));
|
|
|
|
for (curtime = starttime = GetTimeInMillis();
|
|
(curtime - starttime) < timeout_ms; curtime = GetTimeInMillis())
|
|
{
|
|
if ((INREG(PORT_HOTPLUG_EN) & CRT_HOTPLUG_FORCE_DETECT) == 0)
|
|
break;
|
|
}
|
|
|
|
if ((INREG(PORT_HOTPLUG_STAT) & CRT_HOTPLUG_MONITOR_MASK) ==
|
|
CRT_HOTPLUG_MONITOR_COLOR)
|
|
{
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Detects CRT presence by checking for load.
|
|
*
|
|
* Requires that the current pipe's DPLL is active. This will cause flicker
|
|
* on the CRT, so it should not be used while the display is being used. Only
|
|
* color (not monochrome) displays are detected.
|
|
*/
|
|
static Bool
|
|
i830LoadDetectCRT(ScrnInfoPtr pScrn)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
CARD32 adpa, pipeconf;
|
|
CARD8 st00;
|
|
int pipeconf_reg, bclrpat_reg, dpll_reg;
|
|
int pipe;
|
|
|
|
pipe = pI830->pipe;
|
|
if (pipe == 0) {
|
|
bclrpat_reg = BCLRPAT_A;
|
|
pipeconf_reg = PIPEACONF;
|
|
dpll_reg = DPLL_A;
|
|
} else {
|
|
bclrpat_reg = BCLRPAT_B;
|
|
pipeconf_reg = PIPEBCONF;
|
|
dpll_reg = DPLL_B;
|
|
}
|
|
|
|
/* Don't try this if the DPLL isn't running. */
|
|
if (!(INREG(dpll_reg) & DPLL_VCO_ENABLE))
|
|
return FALSE;
|
|
|
|
adpa = INREG(ADPA);
|
|
|
|
/* Enable CRT output if disabled. */
|
|
if (!(adpa & ADPA_DAC_ENABLE)) {
|
|
OUTREG(ADPA, adpa | ADPA_DAC_ENABLE |
|
|
((pipe == 1) ? ADPA_PIPE_B_SELECT : 0));
|
|
}
|
|
|
|
/* Set the border color to red, green. Maybe we should save/restore this
|
|
* reg.
|
|
*/
|
|
OUTREG(bclrpat_reg, 0x00500050);
|
|
|
|
/* Force the border color through the active region */
|
|
pipeconf = INREG(pipeconf_reg);
|
|
OUTREG(pipeconf_reg, pipeconf | PIPECONF_FORCE_BORDER);
|
|
|
|
/* Read the ST00 VGA status register */
|
|
st00 = pI830->readStandard(pI830, 0x3c2);
|
|
|
|
/* Restore previous settings */
|
|
OUTREG(pipeconf_reg, pipeconf);
|
|
OUTREG(ADPA, adpa);
|
|
|
|
if (st00 & (1 << 4))
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Detects CRT presence by probing for a response on the DDC address.
|
|
*
|
|
* This takes approximately 5ms in testing on an i915GM, with CRT connected or
|
|
* not.
|
|
*/
|
|
static Bool
|
|
i830DDCDetectCRT(ScrnInfoPtr pScrn)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
struct _I830OutputRec *output;
|
|
|
|
output = &pI830->output[0];
|
|
/* CRT should always be at 0, but check anyway */
|
|
if (output->type != I830_OUTPUT_ANALOG)
|
|
return FALSE;
|
|
|
|
return xf86I2CProbeAddress(output->pDDCBus, 0x00A0);
|
|
}
|
|
|
|
/**
|
|
* Attempts to detect CRT presence through any method available.
|
|
*
|
|
* @param allow_disturb enables detection methods that may cause flickering
|
|
* on active displays.
|
|
*/
|
|
Bool
|
|
i830DetectCRT(ScrnInfoPtr pScrn, Bool allow_disturb)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
Bool found_ddc;
|
|
|
|
if (IS_I945G(pI830) || IS_I945GM(pI830))
|
|
return i830HotplugDetectCRT(pScrn);
|
|
|
|
found_ddc = i830DDCDetectCRT(pScrn);
|
|
if (found_ddc)
|
|
return TRUE;
|
|
|
|
/* Use the load-detect method if we're not currently outputting to the CRT,
|
|
* or we don't care.
|
|
*
|
|
* Actually, the method is unreliable currently. We need to not share a
|
|
* pipe, as it seems having other outputs on that pipe will result in a
|
|
* false positive.
|
|
*/
|
|
if (0 && (allow_disturb || !(INREG(ADPA) & !ADPA_DAC_ENABLE))) {
|
|
return i830LoadDetectCRT(pScrn);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Sets the power state for the panel.
|
|
*/
|
|
void
|
|
i830SetLVDSPanelPower(ScrnInfoPtr pScrn, Bool on)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
CARD32 pp_status, pp_control;
|
|
CARD32 blc_pwm_ctl;
|
|
int backlight_duty_cycle;
|
|
|
|
blc_pwm_ctl = INREG (BLC_PWM_CTL);
|
|
backlight_duty_cycle = blc_pwm_ctl & BACKLIGHT_DUTY_CYCLE_MASK;
|
|
if (backlight_duty_cycle)
|
|
pI830->backlight_duty_cycle = backlight_duty_cycle;
|
|
|
|
if (on) {
|
|
OUTREG(PP_STATUS, INREG(PP_STATUS) | PP_ON);
|
|
OUTREG(PP_CONTROL, INREG(PP_CONTROL) | POWER_TARGET_ON);
|
|
do {
|
|
pp_status = INREG(PP_STATUS);
|
|
pp_control = INREG(PP_CONTROL);
|
|
} while (!(pp_status & PP_ON) && !(pp_control & POWER_TARGET_ON));
|
|
OUTREG(BLC_PWM_CTL,
|
|
(blc_pwm_ctl & ~BACKLIGHT_DUTY_CYCLE_MASK) |
|
|
pI830->backlight_duty_cycle);
|
|
} else {
|
|
OUTREG(BLC_PWM_CTL,
|
|
(blc_pwm_ctl & ~BACKLIGHT_DUTY_CYCLE_MASK));
|
|
|
|
OUTREG(PP_STATUS, INREG(PP_STATUS) & ~PP_ON);
|
|
OUTREG(PP_CONTROL, INREG(PP_CONTROL) & ~POWER_TARGET_ON);
|
|
do {
|
|
pp_status = INREG(PP_STATUS);
|
|
pp_control = INREG(PP_CONTROL);
|
|
} while ((pp_status & PP_ON) || (pp_control & POWER_TARGET_ON));
|
|
}
|
|
}
|