563 lines
13 KiB
C
563 lines
13 KiB
C
#include "xf86.h"
|
|
#include "xf86_ansic.h"
|
|
#include "i830.h"
|
|
|
|
static int i830_clock(int refclk, int m1, int m2, int n, int p1, int p2)
|
|
{
|
|
return (refclk * (5 * (m1 + 2) + (m2 + 2)) / (n + 2)) / (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);
|
|
}
|
|
|
|
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_p, max_p;
|
|
|
|
min_p = 5;
|
|
max_p = 80;
|
|
if (pI830->PciInfo->chipType >= PCI_CHIP_I915_G) {
|
|
min_m1 = 10;
|
|
max_m1 = 20;
|
|
min_m2 = 5;
|
|
max_m2 = 9;
|
|
min_m = 70;
|
|
max_m = 120;
|
|
min_n = 3;
|
|
max_n = 8;
|
|
if (outputs & PIPE_LCD_ACTIVE) {
|
|
min_p = 7;
|
|
max_p = 98;
|
|
}
|
|
} else {
|
|
min_m1 = 16;
|
|
max_m1 = 24;
|
|
min_m2 = 7;
|
|
max_m2 = 11;
|
|
min_m = 90;
|
|
max_m = 130;
|
|
min_n = 4;
|
|
max_n = 8;
|
|
if (outputs & PIPE_LCD_ACTIVE) {
|
|
min_n = 3;
|
|
min_m = 88;
|
|
}
|
|
}
|
|
|
|
p = p1 + p2;
|
|
m = 5 * (m1 + 2) + (m2 + 2);
|
|
vco = refclk * m / (n + 2);
|
|
dotclock = i830_clock(refclk, m1, m2, n, p1, p2);
|
|
|
|
if (p1 < 1 || p1 > 8)
|
|
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 + 2 < min_n || n + 2 > max_n) /*XXX: Is the +2 right? */
|
|
return FALSE;
|
|
if (vco < 1400000 || vco > 2800000)
|
|
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 < 20000 || dotclock > 400000)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#if 0
|
|
int
|
|
i830ReadAndReportPLL(ScrnInfoPtr pScrn)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
CARD32 temp, dpll;
|
|
int refclk, m1, m2, n, p1, p2;
|
|
|
|
refclk = 96000; /* XXX: The refclk may be 100000 for the LVDS */
|
|
|
|
dpll = INREG(DPLL_A);
|
|
switch ((dpll & DPLL_FPA01_P1_POST_DIV_MASK) >> 16) {
|
|
case 0x01:
|
|
p1 = 1;
|
|
break;
|
|
case 0x02:
|
|
p1 = 2;
|
|
break;
|
|
case 0x04:
|
|
p1 = 3;
|
|
break;
|
|
case 0x08:
|
|
p1 = 4;
|
|
break;
|
|
case 0x10:
|
|
p1 = 5;
|
|
break;
|
|
case 0x20:
|
|
p1 = 6;
|
|
break;
|
|
case 0x40:
|
|
p1 = 7;
|
|
break;
|
|
case 0x80:
|
|
p1 = 8;
|
|
break;
|
|
default:
|
|
FatalError("Unknown p1 clock div: 0x%x\n",
|
|
dpll & DPLL_FPA01_P1_POST_DIV_MASK);
|
|
}
|
|
|
|
switch (dpll & DPLL_P2_CLOCK_DIV_MASK) {
|
|
case DPLL_DAC_SERIAL_P2_CLOCK_DIV_5:
|
|
p2 = 5;
|
|
break;
|
|
case DPLL_DAC_SERIAL_P2_CLOCK_DIV_10:
|
|
p2 = 10;
|
|
break;
|
|
/* XXX:
|
|
case DPLLB_LVDS_P2_CLOCK_DIV_7:
|
|
p2 = 7;
|
|
break;
|
|
case DPLLB_LVDS_P2_CLOCK_DIV_14:
|
|
p2 = 14;
|
|
break;
|
|
*/
|
|
default:
|
|
FatalError("Unknown p2 clock div: 0x%x\n", dpll & DPLL_P2_CLOCK_DIV_MASK);
|
|
}
|
|
|
|
if (dpll & DISPLAY_RATE_SELECT_FPA1)
|
|
temp = INREG(FPA1);
|
|
else
|
|
temp = INREG(FPA0);
|
|
n = (temp & FP_N_DIV_MASK) >> 16;
|
|
m1 = (temp & FP_M1_DIV_MASK) >> 8;
|
|
m2 = (temp & FP_M2_DIV_MASK);
|
|
|
|
i830PrintPll("FPA", refclk, m1, m2, n, p1, p2);
|
|
ErrorF("clock settings for FPA0 look %s\n",
|
|
i830PllIsValid(refclk, m1, m2, n, p1, p2) ? "good" : "bad");
|
|
ErrorF("clock regs: 0x%08x, 0x%08x\n", dpll, temp);
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
|
|
if (pI830->PciInfo->chipType >= PCI_CHIP_I915_G) {
|
|
min_m1 = 10;
|
|
max_m1 = 20;
|
|
min_m2 = 5;
|
|
max_m2 = 9;
|
|
} else {
|
|
min_m1 = 16;
|
|
max_m1 = 24;
|
|
min_m2 = 7;
|
|
max_m2 = 11;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
for (m1 = min_m1; m1 <= max_m1; m1++) {
|
|
for (m2 = min_m2; m2 < max_m2; m2++) {
|
|
for (n = 1; n <= 6; n++) {
|
|
for (p1 = 1; p1 <= 8; 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, m2, n, p1, p2;
|
|
CARD32 dpll = 0, fp = 0, temp;
|
|
CARD32 htot, hblank, hsync, vtot, vblank, vsync, dspcntr;
|
|
CARD32 pipesrc, dspsize, adpa;
|
|
Bool ok;
|
|
int refclk = 96000;
|
|
int outputs;
|
|
|
|
ErrorF("Requested pix clock: %d\n", pMode->Clock);
|
|
|
|
if (pipe == 0)
|
|
outputs = pI830->operatingDevices & 0xff;
|
|
else
|
|
outputs = (pI830->operatingDevices >> 8) & 0xff;
|
|
|
|
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;
|
|
}
|
|
|
|
ok = i830FindBestPLL(pScrn, outputs, pMode->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 (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;
|
|
}
|
|
if (outputs & (PIPE_TV_ACTIVE | PIPE_TV2_ACTIVE))
|
|
dpll |= PLL_REF_INPUT_TVCLKIN;
|
|
else
|
|
dpll |= PLL_REF_INPUT_DREFCLK;
|
|
dpll |= SDV0_DEFAULT_MULTIPLIER;
|
|
|
|
fp = (n << 16) | (m1 << 8) | m2;
|
|
|
|
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);
|
|
|
|
adpa = INREG(ADPA);
|
|
adpa &= ~(ADPA_HSYNC_ACTIVE_HIGH | ADPA_VSYNC_ACTIVE_HIGH);
|
|
adpa &= ~(ADPA_VSYNC_CNTL_DISABLE | ADPA_HSYNC_CNTL_DISABLE);
|
|
adpa |= ADPA_DAC_ENABLE;
|
|
if (pMode->Flags & V_PHSYNC)
|
|
adpa |= ADPA_HSYNC_ACTIVE_HIGH;
|
|
if (pMode->Flags & V_PVSYNC)
|
|
adpa |= ADPA_VSYNC_ACTIVE_HIGH;
|
|
|
|
i830PrintPll("chosen", refclk, m1, m2, n, p1, p2);
|
|
ErrorF("clock settings for chosen look %s\n",
|
|
i830PllIsValid(pScrn, outputs, refclk, m1, m2, n, p1, p2) ?
|
|
"good" : "bad");
|
|
ErrorF("clock regs: 0x%08x, 0x%08x\n", dpll, 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_16BPP;
|
|
else
|
|
dspcntr |= DISPPLANE_15_16BPP;
|
|
break;
|
|
case 32:
|
|
dspcntr |= DISPPLANE_32BPP;
|
|
break;
|
|
default:
|
|
FatalError("unknown display bpp\n");
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* Next, disable display pipes */
|
|
temp = INREG(PIPEACONF);
|
|
OUTREG(PIPEACONF, temp & ~PIPEACONF_ENABLE);
|
|
|
|
/* Wait for vblank for the disable to take effect */
|
|
i830WaitForVblank(pScrn);
|
|
|
|
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 {
|
|
/* First, disable display planes */
|
|
temp = INREG(DSPBCNTR);
|
|
OUTREG(DSPBCNTR, temp & ~DISPLAY_PLANE_ENABLE);
|
|
|
|
/* Next, disable display pipes */
|
|
temp = INREG(PIPEBCONF);
|
|
OUTREG(PIPEBCONF, temp & ~PIPEBCONF_ENABLE);
|
|
|
|
/* Wait for vblank for the disable to take effect */
|
|
i830WaitForVblank(pScrn);
|
|
|
|
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);
|
|
|
|
/* 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_CRT_ACTIVE)
|
|
OUTREG(ADPA, adpa);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Mode bandwidth is %d Mpixel/s\n",
|
|
(int)(pMode->HDisplay * pMode->VDisplay *
|
|
pMode->VRefresh / 1000000));
|
|
|
|
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");
|
|
|
|
done:
|
|
#ifdef XF86DRI
|
|
if (didLock)
|
|
I830DRIUnlock(pScrn);
|
|
#endif
|
|
|
|
return ok;
|
|
}
|
|
|
|
Bool
|
|
i830DetectCRT(ScrnInfoPtr pScrn)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
CARD32 temp;
|
|
|
|
temp = INREG(PORT_HOTPLUG_EN);
|
|
OUTREG(PORT_HOTPLUG_EN, temp | CRT_HOTPLUG_FORCE_DETECT);
|
|
|
|
/* Wait for the bit to clear to signal detection finished. */
|
|
while (INREG(PORT_HOTPLUG_EN) & CRT_HOTPLUG_FORCE_DETECT)
|
|
;
|
|
|
|
return ((INREG(PORT_HOTPLUG_STAT) & CRT_HOTPLUG_INT_STATUS));
|
|
}
|
|
|
|
/**
|
|
* Sets the power state for the panel.
|
|
*/
|
|
void
|
|
i830SetLVDSPanelPower(ScrnInfoPtr pScrn, Bool on)
|
|
{
|
|
I830Ptr pI830 = I830PTR(pScrn);
|
|
CARD32 pp_status, pp_control;
|
|
|
|
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));
|
|
} else {
|
|
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));
|
|
}
|
|
}
|