/* * Acceleration for the Creator and Creator3D framebuffer - DAC programming. * * Copyright (C) 2000 David S. Miller (davem@redhat.com) * * 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 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 * DAVID MILLER 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ffb.h" #include "ffb_rcache.h" #include "ffb_fifo.h" #include "xf86.h" #include "xf86_OSproc.h" #include "xf86DDC.h" /* * Used for stabilize time after playing with power management on the display */ #ifndef DPMS_SPIN_COUNT #define DPMS_SPIN_COUNT 100 #endif /* DPMS_SPIN_COUNT */ /* Cursor programming */ void FFBDacLoadCursorPos(FFBPtr pFfb, int x, int y) { ffb_dacPtr dac = pFfb->dac; int posval; posval = ((y & 0xffff) << 16) | (x & 0xffff); posval &= (FFBDAC_CUR_POS_Y_SIGN | FFBDAC_CUR_POS_Y | FFBDAC_CUR_POS_X_SIGN | FFBDAC_CUR_POS_X); DACCUR_WRITE(dac, FFBDAC_CUR_POS, posval); } void FFBDacLoadCursorColor(FFBPtr pFfb, int fg, int bg) { ffb_dacPtr dac = pFfb->dac; dac->cur = FFBDAC_CUR_COLOR1; dac->curdata = bg; dac->curdata = fg; } void FFBDacCursorEnableDisable(FFBPtr pFfb, int enable) { ffb_dac_info_t *p = &pFfb->dac_info; ffb_dacPtr dac = pFfb->dac; int val; val = 0; if (!enable) val = (FFBDAC_CUR_CTRL_P0 | FFBDAC_CUR_CTRL_P1); /* PAC1 ramdacs with manufacturing revision less than * '3' invert these control bits, wheee... */ if (p->flags & FFB_DAC_ICURCTL) val ^= (FFBDAC_CUR_CTRL_P0 | FFBDAC_CUR_CTRL_P1); DACCUR_WRITE(dac, FFBDAC_CUR_CTRL, val); } void FFBDacCursorLoadBitmap(FFBPtr pFfb, int xshift, int yshift, unsigned int *bitmap) { ffb_dacPtr dac = pFfb->dac; int i, j; dac->cur = FFBDAC_CUR_BITMAP_P0; for (j = 0; j < 2; j++) { bitmap += yshift * 2; if (!xshift) { for (i = yshift * 2; i < 128; i++) dac->curdata = *bitmap++; } else if (xshift < 32) { for (i = yshift; i < 64; i++, bitmap += 2) { dac->curdata = (bitmap[0] << xshift) | (bitmap[1] >> (32 - xshift)); dac->curdata = bitmap[1] << xshift; } } else { for (i = yshift; i < 64; i++, bitmap += 2) { dac->curdata = bitmap[1] << (xshift - 32); dac->curdata = 0; } } for (i = 0; i < yshift * 2; i++) dac->curdata = 0; } } /* Config space programming */ /* XF86 LoadPalette callback. */ void FFBDacLoadPalette(ScrnInfoPtr pScrn, int ncolors, int *indices, LOCO *colors, VisualPtr pVisual) { FFBPtr pFfb = GET_FFB_FROM_SCRN(pScrn); ffb_dac_info_t *p = &pFfb->dac_info; ffb_dacPtr dac = pFfb->dac; unsigned int *cluts; int i, index, palette; if ((pVisual->nplanes != 8 && pVisual->class != DirectColor) || (pVisual->nplanes == 8 && pVisual->class == StaticGray)) return; palette = 0; if (p->flags & FFB_DAC_PAC2) { if (pVisual->class == PseudoColor) palette = 0; if (pVisual->class == GrayScale) palette = 1; if (pVisual->class == DirectColor) palette = 2; } cluts = &p->x_dac_state.clut[256 * palette]; for (i = 0; i < ncolors; i++) { unsigned int regval; index = indices[i]; if (pVisual->class == GrayScale) { regval = cluts[index] = ((colors[index].red << FFBDAC_COLOR_RED_SHFT) | (colors[index].red << FFBDAC_COLOR_GREEN_SHFT) | (colors[index].red << FFBDAC_COLOR_BLUE_SHFT)); } else { regval = cluts[index] = ((colors[index].red << FFBDAC_COLOR_RED_SHFT) | (colors[index].green << FFBDAC_COLOR_GREEN_SHFT) | (colors[index].blue << FFBDAC_COLOR_BLUE_SHFT)); } FFBLOG(("FFBDacLoadPalette: visclass(%d) index(%d) val[%08x]\n", pVisual->class, index, regval)); /* Now update the hardware copy. */ dac->cfg = FFBDAC_CFG_CLUP(palette) + index; dac->cfgdata = regval; } } /* WARNING: Very dangerous function, use with extreme care. */ static void dac_stop(FFBPtr pFfb) { ffb_dacPtr dac = pFfb->dac; unsigned int tgctrl; tgctrl = DACCFG_READ(dac, FFBDAC_CFG_TGEN); if (tgctrl & FFBDAC_CFG_TGEN_TGE) { long limit = 1000000; /* We try to shut off the timing generation * precisely at the beginning of a vertical * retrace. This is really just to make it * look nice, it's not a functional necessity. * * The limit is so that malfunctioning hardware * does not end up hanging the server. */ while (limit--) { unsigned int vctr = DACCFG_READ(dac, FFBDAC_CFG_TGVC); if (vctr == 0) break; } DACCFG_WRITE(dac, FFBDAC_CFG_TGEN, 0); } } /* This is made slightly complex because the ordering matters * between several operations. We have to stop the DAC while * restoring the timing registers so that some intermediate * state does not emit wild retrace signals to the monitor. * * Another further complication is that we need to mess with * some portions of the FFB framebuffer config registers to * do this all properly. */ static void dac_state_restore(FFBPtr pFfb, ffb_dac_hwstate_t *state) { ffb_dac_info_t *p = &pFfb->dac_info; ffb_dacPtr dac = pFfb->dac; ffb_fbcPtr ffb = pFfb->regs; int i, nluts; /* Step 1: Shut off all pixel timing generation. */ dac_stop(pFfb); ffb->fbcfg0 = 0; /* Step 2: Restore timing settings. */ DACCFG_WRITE(dac, FFBDAC_CFG_VBNP, state->vbnp); DACCFG_WRITE(dac, FFBDAC_CFG_VBAP, state->vbap); DACCFG_WRITE(dac, FFBDAC_CFG_VSNP, state->vsnp); DACCFG_WRITE(dac, FFBDAC_CFG_VSAP, state->vsap); DACCFG_WRITE(dac, FFBDAC_CFG_HSNP, state->hsnp); DACCFG_WRITE(dac, FFBDAC_CFG_HBNP, state->hbnp); DACCFG_WRITE(dac, FFBDAC_CFG_HBAP, state->hbap); DACCFG_WRITE(dac, FFBDAC_CFG_HSYNCNP, state->hsyncnp); DACCFG_WRITE(dac, FFBDAC_CFG_HSYNCAP, state->hsyncap); DACCFG_WRITE(dac, FFBDAC_CFG_HSCENNP, state->hscennp); DACCFG_WRITE(dac, FFBDAC_CFG_HSCENAP, state->hscenap); DACCFG_WRITE(dac, FFBDAC_CFG_EPNP, state->epnp); DACCFG_WRITE(dac, FFBDAC_CFG_EINP, state->einp); DACCFG_WRITE(dac, FFBDAC_CFG_EIAP, state->eiap); /* Step 3: Restore rest of DAC hw state. */ DACCFG_WRITE(dac, FFBDAC_CFG_PPLLCTRL, state->ppllctrl); DACCFG_WRITE(dac, FFBDAC_CFG_GPLLCTRL, state->gpllctrl); DACCFG_WRITE(dac, FFBDAC_CFG_PFCTRL, state->pfctrl); DACCFG_WRITE(dac, FFBDAC_CFG_UCTRL, state->uctrl); nluts = (p->flags & FFB_DAC_PAC1) ? 256 : (4 * 256); dac->cfg = FFBDAC_CFG_CLUP_BASE; for (i = 0; i < nluts; i++) dac->cfgdata = state->clut[i]; if (p->flags & FFB_DAC_PAC2) { dac->cfg = FFBDAC_PAC2_AOVWLUT0; for (i = 0; i < 4; i++) dac->cfgdata = state->ovluts[i]; } DACCFG_WRITE(dac, FFBDAC_CFG_WTCTRL, state->wtctrl); DACCFG_WRITE(dac, FFBDAC_CFG_TMCTRL, state->tmctrl); DACCFG_WRITE(dac, FFBDAC_CFG_TCOLORKEY, state->tcolorkey); if (p->flags & FFB_DAC_PAC2) DACCFG_WRITE(dac, FFBDAC_CFG_WAMASK, state->wamask); if (p->flags & FFB_DAC_PAC1) { dac->cfg = FFBDAC_PAC1_APWLUT_BASE; for (i = 0; i < 32; i++) dac->cfgdata = state->pwluts[i]; } else { dac->cfg = FFBDAC_PAC2_APWLUT_BASE; for (i = 0; i < 64; i++) dac->cfgdata = state->pwluts[i]; } DACCFG_WRITE(dac, FFBDAC_CFG_DACCTRL, state->dacctrl); /* Step 4: Restore FFB framebuffer config state. */ if (pFfb->ffb_type == ffb2_vertical_plus || pFfb->ffb_type == ffb2_horizontal_plus || pFfb->ffb_type == afb_m3 || pFfb->ffb_type == afb_m6) ffb->passin = p->ffb_passin_ctrl; ffb->fbcfg0 = p->ffbcfg0; ffb->fbcfg2 = p->ffbcfg2; /* Step 5: Restore the timing generator control reg. */ DACCFG_WRITE(dac, FFBDAC_CFG_TGEN, state->tgen); /* Step 6: Pause for a bit. */ for (i = 0; i < 100; i++) (void) DACCFG_READ(dac, FFBDAC_CFG_TGVC); } static void dac_state_save(FFBPtr pFfb, ffb_dac_hwstate_t *state) { ffb_dac_info_t *p = &pFfb->dac_info; ffb_dacPtr dac = pFfb->dac; int i, nluts; state->ppllctrl = DACCFG_READ(dac, FFBDAC_CFG_PPLLCTRL); state->gpllctrl = DACCFG_READ(dac, FFBDAC_CFG_GPLLCTRL); state->pfctrl = DACCFG_READ(dac, FFBDAC_CFG_PFCTRL); state->uctrl = DACCFG_READ(dac, FFBDAC_CFG_UCTRL); nluts = (p->flags & FFB_DAC_PAC1) ? 256 : (4 * 256); dac->cfg = FFBDAC_CFG_CLUP_BASE; for (i = 0; i < nluts; i++) state->clut[i] = dac->cfgdata; if (p->flags & FFB_DAC_PAC2) { dac->cfg = FFBDAC_PAC2_AOVWLUT0; for (i = 0; i < 4; i++) state->ovluts[i] = dac->cfgdata; } state->wtctrl = DACCFG_READ(dac, FFBDAC_CFG_WTCTRL); state->tmctrl = DACCFG_READ(dac, FFBDAC_CFG_TMCTRL); state->tcolorkey = DACCFG_READ(dac, FFBDAC_CFG_TCOLORKEY); if (p->flags & FFB_DAC_PAC2) state->wamask = DACCFG_READ(dac, FFBDAC_CFG_WAMASK); if (p->flags & FFB_DAC_PAC1) { dac->cfg = FFBDAC_PAC1_APWLUT_BASE; for (i = 0; i < 32; i++) state->pwluts[i] = dac->cfgdata; } else { dac->cfg = FFBDAC_PAC2_APWLUT_BASE; for (i = 0; i < 64; i++) state->pwluts[i] = dac->cfgdata; } state->dacctrl = DACCFG_READ(dac, FFBDAC_CFG_DACCTRL); state->tgen = DACCFG_READ(dac, FFBDAC_CFG_TGEN); state->vbnp = DACCFG_READ(dac, FFBDAC_CFG_VBNP); state->vbap = DACCFG_READ(dac, FFBDAC_CFG_VBAP); state->vsnp = DACCFG_READ(dac, FFBDAC_CFG_VSNP); state->vsap = DACCFG_READ(dac, FFBDAC_CFG_VSAP); state->hsnp = DACCFG_READ(dac, FFBDAC_CFG_HSNP); state->hbnp = DACCFG_READ(dac, FFBDAC_CFG_HBNP); state->hbap = DACCFG_READ(dac, FFBDAC_CFG_HBAP); state->hsyncnp = DACCFG_READ(dac, FFBDAC_CFG_HSYNCNP); state->hsyncap = DACCFG_READ(dac, FFBDAC_CFG_HSYNCAP); state->hscennp = DACCFG_READ(dac, FFBDAC_CFG_HSCENNP); state->hscenap = DACCFG_READ(dac, FFBDAC_CFG_HSCENAP); state->epnp = DACCFG_READ(dac, FFBDAC_CFG_EPNP); state->einp = DACCFG_READ(dac, FFBDAC_CFG_EINP); state->eiap = DACCFG_READ(dac, FFBDAC_CFG_EIAP); } static void init_dac_flags(FFBPtr pFfb) { ffb_dac_info_t *p = &pFfb->dac_info; ffb_dacPtr dac = pFfb->dac; unsigned int did, manuf_rev, partnum; char *device; /* Fetch kernel WID. */ p->kernel_wid = *((volatile unsigned char *)pFfb->dfb8x); /* For AFB, assume it is PAC2 which also implies not having * the inverted cursor control attribute. */ if (pFfb->ffb_type == afb_m3 || pFfb->ffb_type == afb_m6) { p->flags = FFB_DAC_PAC2; manuf_rev = 4; } else { p->flags = 0; did = DACCFG_READ(dac, FFBDAC_CFG_DID); manuf_rev = DACCFG_READ(dac, FFBDAC_CFG_UCTRL); manuf_rev = (manuf_rev & FFBDAC_UCTRL_MANREV) >> 8; partnum = ((did & FFBDAC_CFG_DID_PNUM) >> 12); if (partnum == 0x236e) p->flags |= FFB_DAC_PAC2; else p->flags |= FFB_DAC_PAC1; } device = pFfb->psdp->device; if ((p->flags & FFB_DAC_PAC1) != 0) { if (manuf_rev < 3) { p->flags |= FFB_DAC_ICURCTL; xf86Msg(X_INFO, "%s: BT9068 (PAC1) ramdac detected (with " "inverted cursor control)\n", device); } else { xf86Msg(X_INFO, "%s: BT9068 (PAC1) ramdac detected (with " "normal cursor control)\n", device); } } else { xf86Msg(X_INFO, "%s: BT498 (PAC2) ramdac detected\n", device); } } /* The registers of the chip must be mapped, and the FFB/AFB * board type must be probed before this is invoked. */ Bool FFBDacInit(FFBPtr pFfb) { ffb_dac_info_t *p = &pFfb->dac_info; ffb_fbcPtr ffb = pFfb->regs; init_dac_flags(pFfb); p->ffbcfg0 = ffb->fbcfg0; p->ffbcfg2 = ffb->fbcfg2; if (pFfb->ffb_type == ffb2_vertical_plus || pFfb->ffb_type == ffb2_horizontal_plus || pFfb->ffb_type == afb_m3 || pFfb->ffb_type == afb_m6) p->ffb_passin_ctrl = ffb->passin; /* Save the kernel DAC state. We also save to the * X server state here as well even though we have * not modified anything yet. */ dac_state_save(pFfb, &p->kern_dac_state); dac_state_save(pFfb, &p->x_dac_state); /* Fire up the WID layer. */ FFBWidPoolInit(pFfb); return TRUE; } /* We need to reset the A buffer X planes to the value 0xff * when giving the hardware back to the kernel too, thus... * Also need to do this for the B buffer X planes when double * buffering is available. */ static void restore_kernel_xchannel(FFBPtr pFfb) { ffb_fbcPtr ffb = pFfb->regs; unsigned int fbc, ppc, ppc_mask, drawop, wid; wid = pFfb->dac_info.kernel_wid; if (pFfb->has_double_buffer) fbc = FFB_FBC_WB_AB; else fbc = FFB_FBC_WB_A; fbc |= (FFB_FBC_WM_COMBINED | FFB_FBC_RB_A | FFB_FBC_SB_BOTH | FFB_FBC_ZE_OFF | FFB_FBC_YE_OFF | FFB_FBC_XE_ON | FFB_FBC_RGBE_MASK); ppc = (FFB_PPC_APE_DISABLE | FFB_PPC_CS_CONST | FFB_PPC_XS_WID); ppc_mask = (FFB_PPC_APE_MASK | FFB_PPC_CS_MASK | FFB_PPC_XS_MASK); drawop = FFB_DRAWOP_RECTANGLE; FFB_ATTR_RAW(pFfb, ppc, ppc_mask, ~0, (FFB_ROP_EDIT_BIT | GXcopy)|(FFB_ROP_NEW<<8), drawop, 0x0, fbc, wid); FFBFifo(pFfb, 4); FFB_WRITE64(&ffb->by, 0, 0); FFB_WRITE64_2(&ffb->bh, pFfb->psdp->height, pFfb->psdp->width); pFfb->rp_active = 1; FFBWait(pFfb, ffb); } void FFBDacFini(FFBPtr pFfb) { ffb_dac_info_t *p = &pFfb->dac_info; /* Just restore the kernel ramdac/x-channel state. */ dac_state_restore(pFfb, &p->kern_dac_state); restore_kernel_xchannel(pFfb); } /* Restore X server DAC state. */ void FFBDacEnterVT(FFBPtr pFfb) { ffb_dac_info_t *p = &pFfb->dac_info; /* Save kernel DAC state. */ dac_state_save(pFfb, &p->kern_dac_state); /* Restore X DAC state. */ dac_state_restore(pFfb, &p->x_dac_state); } /* Restore kernel DAC state. */ void FFBDacLeaveVT(FFBPtr pFfb) { ffb_dac_info_t *p = &pFfb->dac_info; /* Save X DAC state. */ dac_state_save(pFfb, &p->x_dac_state); /* Restore kernel DAC and x-channel state. */ dac_state_restore(pFfb, &p->kern_dac_state); restore_kernel_xchannel(pFfb); } /* DPMS stuff, courtesy of a hint from David S. Miller. * 05.xii.01, FEM */ /* * I don't know why, if at all, this is needed, but JJ or DSM do it * on restore. I observe that when just blanking/unblanking, everything * works fine without it, but that sometimes DPMS -> Standby actually * results in Off. Maybe related? */ static void SPIN(ffb_dacPtr d, int count) { while(count-- > 0) { (void) DACCFG_READ(d, FFBDAC_CFG_TGVC); } return; } /* Screen save (blank) restore */ Bool FFBDacSaveScreen(FFBPtr pFfb, int mode) { int tmp; ffb_dacPtr dac; if(!pFfb) return FALSE; /* Is there any way at all this could happen? */ else dac = pFfb -> dac; tmp = DACCFG_READ(dac, FFBDAC_CFG_TGEN); /* Get the timing information */ switch(mode) { case SCREEN_SAVER_ON: case SCREEN_SAVER_CYCLE: tmp &= ~FFBDAC_CFG_TGEN_VIDE; /* Kill the video */ break; case SCREEN_SAVER_OFF: case SCREEN_SAVER_FORCER: tmp |= FFBDAC_CFG_TGEN_VIDE; /* Turn the video on */ break; default: return FALSE; /* Don't know what to do; gently fail. */ } DACCFG_WRITE(dac, FFBDAC_CFG_TGEN, tmp); /* Restore timing register, video set as asked */ SPIN(dac, DPMS_SPIN_COUNT/10); return TRUE; } /* DPMS Control, also hinted at by David Miller. The rule seems to be: StandBy = -HSYNC +VSYNC -VIDEO Suspend = +HSYNC -VSYNC -VIDEO Off = -HSYNC -VSYNC -VIDEO On = +HSYNC +VSINC +VIDEO If you don't force video off, someone periodically tries to turn the monitor on for some reason. I don't know who or why, so I kill the video when trying to go into some sort of energy saving mode. (In real life, 'xset s blank s xx' could well have taken care of this.) Also, on MY monitor, StandBy as above defined (-H+V-Vid) in fact gives the same as Off, which I don't want. Hence, I just do (-Vid) 05.xii.01, FEM 08.xii.01, FEM */ void FFBDacDPMSMode(FFBPtr pFfb, int DPMSMode, int flags) { int tmp; ffb_dacPtr dac = pFfb -> dac; tmp = DACCFG_READ(dac, FFBDAC_CFG_TGEN); /* Get timing control */ switch(DPMSMode) { case DPMSModeOn: tmp &= ~(FFBDAC_CFG_TGEN_VSD | FFBDAC_CFG_TGEN_HSD); /* Turn off VSYNC, HSYNC disable bits */ tmp |= FFBDAC_CFG_TGEN_VIDE; /* Turn the video on */ break; case DPMSModeStandby: #ifdef DPMS_TRUE_STANDBY tmp |= FFBDAC_CFG_TGEN_HSD; /* HSYNC = OFF */ #endif /* DPMS_TRUE_STANDBY */ tmp &= ~FFBDAC_CFG_TGEN_VSD; /* VSYNC = ON */ tmp &= ~FFBDAC_CFG_TGEN_VIDE; /* Kill the video */ break; case DPMSModeSuspend: tmp |= FFBDAC_CFG_TGEN_VSD; /* VSYNC = OFF */ tmp &= ~FFBDAC_CFG_TGEN_HSD; /* HSYNC = ON */ tmp &= ~FFBDAC_CFG_TGEN_VIDE; /* Kill the video */ break; case DPMSModeOff: tmp |= (FFBDAC_CFG_TGEN_VSD | FFBDAC_CFG_TGEN_HSD); /* Kill HSYNC, VSYNC both */ tmp &= ~FFBDAC_CFG_TGEN_VIDE; /* Kill the video */ break; default: return; /* If we get here, we really should log an error */ } DACCFG_WRITE(dac, FFBDAC_CFG_TGEN,tmp); /* Restore timing register, video set as asked */ SPIN(dac, DPMS_SPIN_COUNT); /* Is this necessary? Why? */ }