 /*
  * <LIC_AMD_STD>
  * Copyright (C) 2005 Advanced Micro Devices, Inc.  All Rights Reserved.
  * </LIC_AMD_STD>
  *
  * <CTL_AMD_STD>
  * </CTL_AMD_STD>
  *
  * <DOC_AMD_STD>
  * Cimarron display controller routines.  These routines program the display
  * mode and configure the hardware cursor and video buffers.
  * </DOC_AMD_STD>
  *
  */

/*---------------------*/
/* CIMARRON VG GLOBALS */
/*---------------------*/

CIMARRON_STATIC unsigned long vg3_x_hotspot      = 0;
CIMARRON_STATIC unsigned long vg3_y_hotspot      = 0;
CIMARRON_STATIC unsigned long vg3_cursor_offset  = 0;
CIMARRON_STATIC unsigned long vg3_mode_width     = 0;
CIMARRON_STATIC unsigned long vg3_mode_height    = 0;
CIMARRON_STATIC unsigned long vg3_panel_width    = 0;
CIMARRON_STATIC unsigned long vg3_panel_height   = 0;
CIMARRON_STATIC unsigned long vg3_delta_x        = 0;
CIMARRON_STATIC unsigned long vg3_delta_y        = 0;
CIMARRON_STATIC unsigned long vg3_bpp            = 0;

CIMARRON_STATIC unsigned long vg3_color_cursor   = 0;
CIMARRON_STATIC unsigned long vg3_panel_enable   = 0;

/*---------------------------------------------------------------------------
 * vg_delay_milliseconds
 *
 * This routine delays for a number of milliseconds based on a crude
 * delay loop.
 *---------------------------------------------------------------------------*/

int vg_delay_milliseconds (unsigned long ms)
{
	/* ASSUME 500 MHZ 20 CLOCKS PER READ */

	unsigned long loop = ms * 25000;
	while (loop-- > 0)
	{
		READ_REG32 (DC3_UNLOCK);
	}
	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_display_mode
 *
 * This routine sets a CRT display mode using predefined Cimarron timings.  The
 * source width and height are specified to allow scaling.
 *---------------------------------------------------------------------------*/

int vg_set_display_mode (unsigned long src_width, unsigned long src_height,
	unsigned long dst_width, unsigned long dst_height, int bpp, int hz,
    unsigned long flags)
{
	VG_QUERY_MODE crt_query;
	VG_DISPLAY_MODE crt_mode;
	int mode;

	crt_query.active_width  = dst_width;
	crt_query.active_height = dst_height;
	crt_query.bpp           = bpp;
	crt_query.hz            = hz;
	crt_query.query_flags   = VG_QUERYFLAG_ACTIVEWIDTH  |
		                      VG_QUERYFLAG_ACTIVEHEIGHT |
							  VG_QUERYFLAG_BPP          |
							  VG_QUERYFLAG_REFRESH;

	mode = vg_get_display_mode_index (&crt_query);
	if (mode >= 0)
	{
		crt_mode = CimarronDisplayModes[mode];
		crt_mode.src_width  = src_width;
		crt_mode.src_height = src_height;

        /* ADD USER-REQUESTED FLAGS */

        crt_mode.flags |= (flags & VG_MODEFLAG_VALIDUSERFLAGS);

        if (flags & VG_MODEFLAG_OVERRIDE_BAND)
        {
            crt_mode.flags &= ~VG_MODEFLAG_BANDWIDTHMASK;
            crt_mode.flags |= (flags & VG_MODEFLAG_BANDWIDTHMASK);
        }
        if (flags & VG_MODEFLAG_INT_OVERRIDE)
        {
            crt_mode.flags &= ~VG_MODEFLAG_INT_MASK;
            crt_mode.flags |= (flags & VG_MODEFLAG_INT_MASK);
        }

		return vg_set_custom_mode (&crt_mode, bpp);
	}
	return CIM_STATUS_ERROR;
}

/*---------------------------------------------------------------------------
 * vg_set_panel_mode
 *
 * This routine sets a panel mode using predefined Cimarron fixed timings.  The
 * source width and height specify the width and height of the data in the frame
 * buffer.  The destination width and height specify the width and height of
 * the active data to be displayed.  The panel width and height specify the
 * dimensions of the panel.  This interface allows the user to scale or center
 * graphics data or both.  To perform scaling, the src width or height should
 * be different than the destination width or height.  To perform centering or
 * panning, the destination width and height should be different than the panel
 * resolution.
 *---------------------------------------------------------------------------*/

int vg_set_panel_mode (unsigned long src_width, unsigned long src_height,
	unsigned long dst_width,   unsigned long dst_height,
	unsigned long panel_width, unsigned long panel_height,
	int bpp, unsigned long flags)
{
    unsigned long sync_width;
    unsigned long sync_offset;
	VG_QUERY_MODE panel_query;
	VG_DISPLAY_MODE panel_mode;
	int mode;

	/* SEARCH CIMARRON'S TABLE OF PREDEFINED PANEL MODES                   */
	/* If the destination resolution is larger than the panel resolution,  */
	/* panning will be performed.  However, the timings for a panned mode  */
	/* are identical to the timings without panning.  To save space in the */
	/* mode tables, there are no additional table entries for modes with   */
	/* panning.  Instead, we read the timings for a mode without panning   */
	/* and override the structure entries that specify the width and       */
	/* height of the mode.  We perform a similar procedure for centered    */
    /* modes, except that certain timing parameters are dynamically        */
    /* calculated.                                                         */

	panel_query.active_width  = panel_width;
	panel_query.active_height = panel_height;
	panel_query.panel_width   = panel_width;
	panel_query.panel_height  = panel_height;
	panel_query.bpp           = bpp;
	panel_query.query_flags   = VG_QUERYFLAG_ACTIVEWIDTH  |
		                        VG_QUERYFLAG_ACTIVEHEIGHT |
							    VG_QUERYFLAG_PANELWIDTH   |
							    VG_QUERYFLAG_PANELHEIGHT  |
								VG_QUERYFLAG_PANEL        |
							    VG_QUERYFLAG_BPP;

	mode = vg_get_display_mode_index (&panel_query);

	/* COPY THE DATA FROM THE MODE TABLE TO A TEMPORARY STRUCTURE */

	if (mode >= 0)
	{
		panel_mode = CimarronDisplayModes[mode];
		panel_mode.mode_width  = dst_width;
		panel_mode.mode_height = dst_height;
		panel_mode.src_width   = src_width;
		panel_mode.src_height  = src_height;

        /* ADD USER-REQUESTED FLAGS */

        panel_mode.flags |= (flags & VG_MODEFLAG_VALIDUSERFLAGS);

        if (flags & VG_MODEFLAG_OVERRIDE_BAND)
        {
            panel_mode.flags &= ~VG_MODEFLAG_BANDWIDTHMASK;
            panel_mode.flags |= (flags & VG_MODEFLAG_BANDWIDTHMASK);
        }
        if (flags & VG_MODEFLAG_INT_OVERRIDE)
        {
            panel_mode.flags &= ~VG_MODEFLAG_INT_MASK;
            panel_mode.flags |= (flags & VG_MODEFLAG_INT_MASK);
        }

        /* DYNAMICALLY CALCULATE CENTERED TIMINGS */
        /* For centered timings the blank start and blank end are set to  */
        /* half the difference between the mode dimension and the panel   */
        /* dimension.  The sync pulse preserves the width and offset from */
        /* blanking whenever possible.                                    */

        if (dst_width < panel_width)
        {
            sync_width  = panel_mode.hsyncend   - panel_mode.hsyncstart;
            sync_offset = panel_mode.hsyncstart - panel_mode.hblankstart;

            panel_mode.hactive     = dst_width;
            panel_mode.hblankstart = panel_mode.hactive + ((panel_width - dst_width) >> 1);
            panel_mode.hblankend   = panel_mode.htotal - ((panel_width - dst_width) >> 1);
            panel_mode.hsyncstart  = panel_mode.hblankstart + sync_offset;
            panel_mode.hsyncend    = panel_mode.hsyncstart + sync_width;

            panel_mode.flags |= VG_MODEFLAG_CENTERED;
        }
        if (dst_height < panel_height)
        {
            sync_width  = panel_mode.vsyncend   - panel_mode.vsyncstart;
            sync_offset = panel_mode.vsyncstart - panel_mode.vblankstart;

            panel_mode.vactive     = dst_height;
            panel_mode.vblankstart = panel_mode.vactive + ((panel_height - dst_height) >> 1);
            panel_mode.vblankend   = panel_mode.vtotal - ((panel_height - dst_height) >> 1);
            panel_mode.vsyncstart  = panel_mode.vblankstart + sync_offset;
            panel_mode.vsyncend    = panel_mode.vsyncstart + sync_width;

            panel_mode.flags |= VG_MODEFLAG_CENTERED;
        }
		return vg_set_custom_mode (&panel_mode, bpp);
	}
	return CIM_STATUS_ERROR;
}

/*---------------------------------------------------------------------------
 * vg_set_tv_mode
 *
 * This routine sets a TV display mode using predefined Cimarron timings.  The
 * source width and height are specified to allow scaling.
 *---------------------------------------------------------------------------*/

int vg_set_tv_mode (unsigned long *src_width, unsigned long *src_height,
    unsigned long encoder, unsigned long tvres, int bpp, unsigned long flags,
    unsigned long h_overscan, unsigned long v_overscan)
{
    unsigned long sync_width;
    unsigned long sync_offset;
	VG_QUERY_MODE tv_query;
	VG_DISPLAY_MODE tv_mode;
	int mode;

    if (!src_width || !src_height)
        return CIM_STATUS_INVALIDPARAMS;

	tv_query.bpp           = bpp;
    tv_query.encoder       = encoder;
    tv_query.tvmode        = tvres;
	tv_query.query_flags   = VG_QUERYFLAG_BPP          |
                             VG_QUERYFLAG_TVOUT        |
                             VG_QUERYFLAG_ENCODER      |
                             VG_QUERYFLAG_TVMODE;

	mode = vg_get_display_mode_index (&tv_query);
	if (mode >= 0)
	{
        /* RETRIEVE THE UNSCALED RESOLUTION */
        /* As we are indexing here simply by a mode and encoder, the actual      */
        /* timings may vary.  A 0 value for source or height will thus query the */
        /* unscaled resolution.                                                  */

        if (!(*src_width) || !(*src_height))
        {
            *src_width  = CimarronDisplayModes[mode].hactive - (h_overscan << 1);
            *src_height = CimarronDisplayModes[mode].vactive;

            if (CimarronDisplayModes[mode].flags & VG_MODEFLAG_INTERLACED)
            {
                if (((flags & VG_MODEFLAG_INT_OVERRIDE) &&
                      (flags & VG_MODEFLAG_INT_MASK) == VG_MODEFLAG_INT_LINEDOUBLE) ||
                   (!(flags & VG_MODEFLAG_INT_OVERRIDE) &&
                      (CimarronDisplayModes[mode].flags & VG_MODEFLAG_INT_MASK) == VG_MODEFLAG_INT_LINEDOUBLE))
                {
                    if (CimarronDisplayModes[mode].vactive_even > CimarronDisplayModes[mode].vactive)
                        *src_height = CimarronDisplayModes[mode].vactive_even;

                    /* ONLY 1/2 THE OVERSCAN FOR LINE DOUBLED MODES */

                    *src_height -= v_overscan;
                }
                else
                {
                    *src_height += CimarronDisplayModes[mode].vactive_even;
                    *src_height -= v_overscan << 1;
                }
            }
            else
            {
                *src_height -= v_overscan << 1;
            }

            return CIM_STATUS_OK;
        }

		tv_mode = CimarronDisplayModes[mode];
		tv_mode.src_width  = *src_width;
		tv_mode.src_height = *src_height;

        /* ADD USER-REQUESTED FLAGS */

        tv_mode.flags |= (flags & VG_MODEFLAG_VALIDUSERFLAGS);

        if (flags & VG_MODEFLAG_OVERRIDE_BAND)
        {
            tv_mode.flags &= ~VG_MODEFLAG_BANDWIDTHMASK;
            tv_mode.flags |= (flags & VG_MODEFLAG_BANDWIDTHMASK);
        }
        if (flags & VG_MODEFLAG_INT_OVERRIDE)
        {
            tv_mode.flags &= ~VG_MODEFLAG_INT_MASK;
            tv_mode.flags |= (flags & VG_MODEFLAG_INT_MASK);
        }

        /* ADJUST FOR OVERSCAN */

        if (h_overscan)
        {
            sync_width  = tv_mode.hsyncend   - tv_mode.hsyncstart;
            sync_offset = tv_mode.hsyncstart - tv_mode.hblankstart;

            tv_mode.hactive    -= h_overscan << 1;
            tv_mode.hblankstart = tv_mode.hactive + h_overscan;
            tv_mode.hblankend   = tv_mode.htotal  - h_overscan;
            tv_mode.hsyncstart  = tv_mode.hblankstart + sync_offset;
            tv_mode.hsyncend    = tv_mode.hsyncstart  + sync_width;

            tv_mode.flags |= VG_MODEFLAG_CENTERED;
        }
        if (v_overscan)
        {
            sync_width  = tv_mode.vsyncend   - tv_mode.vsyncstart;
            sync_offset = tv_mode.vsyncstart - tv_mode.vblankstart;

            if (tv_mode.flags & VG_MODEFLAG_INTERLACED)
            {
                tv_mode.vactive    -= v_overscan;
                tv_mode.vblankstart = tv_mode.vactive + (v_overscan >> 1);
                tv_mode.vblankend   = tv_mode.vtotal  - (v_overscan >> 1);
                tv_mode.vsyncstart  = tv_mode.vblankstart + sync_offset;
                tv_mode.vsyncend    = tv_mode.vsyncstart  + sync_width;

                sync_width  = tv_mode.vsyncend_even   - tv_mode.vsyncstart_even;
                sync_offset = tv_mode.vsyncstart_even - tv_mode.vblankstart_even;

                tv_mode.vactive_even    -= v_overscan;
                tv_mode.vblankstart_even = tv_mode.vactive_even + (v_overscan >> 1);
                tv_mode.vblankend_even   = tv_mode.vtotal_even  - (v_overscan >> 1);
                tv_mode.vsyncstart_even  = tv_mode.vblankstart_even + sync_offset;
                tv_mode.vsyncend_even    = tv_mode.vsyncstart_even  + sync_width;
            }
            else
            {
                tv_mode.vactive    -= v_overscan << 1;
                tv_mode.vblankstart = tv_mode.vactive + v_overscan;
                tv_mode.vblankend   = tv_mode.vtotal  - v_overscan;
                tv_mode.vsyncstart  = tv_mode.vblankstart + sync_offset;
                tv_mode.vsyncend    = tv_mode.vsyncstart  + sync_width;
            }

            tv_mode.flags |= VG_MODEFLAG_CENTERED;
        }

        /* TV MODES WILL NEVER ALLOW PANNING */

        tv_mode.panel_width  = tv_mode.hactive;
        tv_mode.panel_height = tv_mode.vactive;
        tv_mode.mode_width   = tv_mode.hactive;
        tv_mode.mode_height  = tv_mode.vactive;

		return vg_set_custom_mode (&tv_mode, bpp);
	}
	return CIM_STATUS_ERROR;
}

/*---------------------------------------------------------------------------
 * vg_set_custom_mode
 *
 * This routine sets a display mode.  The API is structured such that this routine
 * can be called from four sources:
 *   - vg_set_display_mode
 *   - vg_set_panel_mode
 *   - vg_set_tv_mode
 *   - directly by the user for a custom mode.
 *---------------------------------------------------------------------------*/

int vg_set_custom_mode (VG_DISPLAY_MODE *mode_params, int bpp)
{
	unsigned long config, misc, temp;
    unsigned long irq_ctl, genlk_ctl;
	unsigned long unlock, flags;
	unsigned long acfg, gcfg, dcfg;
	unsigned long size, line_size, pitch;
	unsigned long bpp_mask, dv_size;
	unsigned long hscale, vscale, starting_width;
    unsigned long starting_height, output_height;
    Q_WORD msr_value;

    /* DETERMINE DIMENSIONS FOR SCALING */
    /* Scaling is performed before flicker filtering and interlacing */

    output_height = mode_params->vactive;

    if (mode_params->flags & VG_MODEFLAG_INTERLACED)
    {
        /* EVEN AND ODD FIELDS ARE SEPARATE */
        /* The composite image height is the sum of the height of both fields */

        if ((mode_params->flags & VG_MODEFLAG_INT_MASK) == VG_MODEFLAG_INT_FLICKER ||
            (mode_params->flags & VG_MODEFLAG_INT_MASK) == VG_MODEFLAG_INT_ADDRESS)
        {
            output_height += mode_params->vactive_even;
        }

        /* LINE DOUBLING */
        /* The composite image height is the greater of the two field heights. */

        else if (mode_params->vactive_even > output_height)
            output_height = mode_params->vactive_even;
    }

	/* CHECK FOR VALID SCALING FACTOR */
	/* GeodeLX supports only 2:1 vertical downscale (before interlacing) and */
	/* 2:1 horizontal downscale.  The source width when scaling must be      */
    /* less than or equal to 1024 pixels.  The destination can be any size,  */
    /* except when flicker filtering is enabled.                             */

    irq_ctl = 0;
    if (mode_params->flags & VG_MODEFLAG_PANELOUT)
    {
        if (mode_params->src_width != mode_params->mode_width)
	    {
            starting_width = (mode_params->hactive * mode_params->src_width) / mode_params->mode_width;
		    hscale = (mode_params->src_width << 14) / (mode_params->mode_width - 1);
		    irq_ctl |= (DC3_IRQFILT_ALPHA_FILT_EN | DC3_IRQFILT_GFX_FILT_EN);
	    }
	    else
	    {
            starting_width = mode_params->hactive;
		    hscale = 0x4000;
	    }
	    if (mode_params->src_height != mode_params->mode_height)
	    {
            starting_height = (output_height * mode_params->src_height) / mode_params->mode_height;
		    vscale = (mode_params->src_height << 14) / (mode_params->mode_height - 1);
		    irq_ctl |= (DC3_IRQFILT_ALPHA_FILT_EN | DC3_IRQFILT_GFX_FILT_EN);
	    }
	    else
	    {
            starting_height = output_height;
		    vscale = 0x4000;
	    }
    }
    else
    {
        starting_width  = mode_params->src_width;
        starting_height = mode_params->src_height;
	    if (mode_params->src_width != mode_params->hactive)
	    {
		    hscale = (mode_params->src_width << 14) / (mode_params->hactive - 1);
		    irq_ctl |= (DC3_IRQFILT_ALPHA_FILT_EN | DC3_IRQFILT_GFX_FILT_EN);
	    }
	    else
	    {
		    hscale = 0x4000;
	    }
	    if (mode_params->src_height != output_height)
	    {
		    vscale = (mode_params->src_height << 14) / (output_height - 1);
		    irq_ctl |= (DC3_IRQFILT_ALPHA_FILT_EN | DC3_IRQFILT_GFX_FILT_EN);
	    }
	    else
	    {
		    vscale = 0x4000;
	    }
    }

    starting_width = (starting_width + 7) & 0xFFFF8;

    if (mode_params->hactive < (starting_width  >> 1) ||
        output_height < (starting_height >> 1)        ||
       (irq_ctl && (starting_width > 1024)))
    {
        return CIM_STATUS_INVALIDSCALE;
    }

    /* VERIFY INTERLACED SCALING */
    /* The output width must be less than or equal to 1024 pixels when the */
    /* flicker filter is enabled.  Also, scaling should be disabled when   */
    /* the interlacing mode is set to interlaced addressing.               */

    if (mode_params->flags & VG_MODEFLAG_INTERLACED)
    {
        if ((((mode_params->flags & VG_MODEFLAG_INT_MASK) == VG_MODEFLAG_INT_FLICKER) &&
               (mode_params->hactive > 1024))                                                 ||
            (((mode_params->flags & VG_MODEFLAG_INT_MASK) == VG_MODEFLAG_INT_ADDRESS) && irq_ctl))
        {
            return CIM_STATUS_INVALIDSCALE;
        }
    }
			
	/* CHECK FOR VALID BPP */
	
	switch (bpp)
	{
		case 8:  bpp_mask = DC3_DCFG_DISP_MODE_8BPP;  break;
	    case 24: bpp_mask = DC3_DCFG_DISP_MODE_24BPP; break;
		case 32: bpp_mask = DC3_DCFG_DISP_MODE_32BPP; break;
		case 12: bpp_mask = DC3_DCFG_DISP_MODE_16BPP | DC3_DCFG_12BPP; break;
		case 15: bpp_mask = DC3_DCFG_DISP_MODE_16BPP | DC3_DCFG_15BPP; break;
		case 16: bpp_mask = DC3_DCFG_DISP_MODE_16BPP | DC3_DCFG_16BPP; break;
		default: return CIM_STATUS_INVALIDPARAMS;
	}

	vg3_bpp = bpp;

	/* CLEAR PANNING OFFSETS */

	vg3_delta_x = 0;
	vg3_delta_y = 0;

	/* SAVE PANEL PARAMETERS */

	if (mode_params->flags & VG_MODEFLAG_PANELOUT)
	{
		vg3_panel_enable = 1;
		vg3_panel_width  = mode_params->panel_width;
		vg3_panel_height = mode_params->panel_height;
		vg3_mode_width   = mode_params->mode_width;
		vg3_mode_height  = mode_params->mode_height;

        /* INVERT THE SHIFT CLOCK IF REQUESTED */
        /* Note that we avoid writing the power management register if */
        /* we can help it.                                             */

        temp = READ_VID32 (DF_POWER_MANAGEMENT);
        if ((mode_params->flags & VG_MODEFLAG_INVERT_SHFCLK) &&
           !(temp & DF_PM_INVERT_SHFCLK))
        {
            WRITE_VID32 (DF_POWER_MANAGEMENT, (temp | DF_PM_INVERT_SHFCLK));
        }
        else if (!(mode_params->flags & VG_MODEFLAG_INVERT_SHFCLK) &&
                  (temp & DF_PM_INVERT_SHFCLK))
        {
            WRITE_VID32 (DF_POWER_MANAGEMENT, (temp & ~DF_PM_INVERT_SHFCLK));
        }

        /* SET PANEL TIMING VALUES */

        if (!(mode_params->flags & VG_MODEFLAG_NOPANELTIMINGS))
        {
            unsigned long pmtim1, pmtim2, dith_ctl;

            if (mode_params->flags & VG_MODEFLAG_XVGA_TFT)
            {
                pmtim1   = DF_DEFAULT_XVGA_PMTIM1;
                pmtim2   = DF_DEFAULT_XVGA_PMTIM2;
                dith_ctl = DF_DEFAULT_DITHCTL;
                msr_value.low  = DF_DEFAULT_XVGA_PAD_SEL_LOW;
                msr_value.high = DF_DEFAULT_XVGA_PAD_SEL_HIGH;
            }
            else if (mode_params->flags & VG_MODEFLAG_CUSTOM_PANEL)
            {
                pmtim1   = mode_params->panel_tim1;
                pmtim2   = mode_params->panel_tim2;
                dith_ctl = mode_params->panel_dither_ctl;
                msr_value.low  = mode_params->panel_pad_sel_low;
                msr_value.high = mode_params->panel_pad_sel_high;
            }
            else
            {
                pmtim1   = DF_DEFAULT_TFT_PMTIM1;
                pmtim2   = DF_DEFAULT_TFT_PMTIM2;
                dith_ctl = DF_DEFAULT_DITHCTL;
                msr_value.low  = DF_DEFAULT_TFT_PAD_SEL_LOW;
                msr_value.high = DF_DEFAULT_TFT_PAD_SEL_HIGH;

            }
            WRITE_VID32 (DF_VIDEO_PANEL_TIM1, pmtim1);
            WRITE_VID32 (DF_VIDEO_PANEL_TIM2, pmtim2);
            WRITE_VID32 (DF_DITHER_CONTROL, dith_ctl);
            msr_write64 (MSR_DEVICE_GEODELX_DF, DF_MSR_PAD_SEL, &msr_value);
        }

        /* SET APPROPRIATE PANEL OUTPUT MODE */

        msr_read64 (MSR_DEVICE_GEODELX_DF, MSR_GEODELINK_CONFIG, &msr_value);

        msr_value.low &= ~DF_CONFIG_OUTPUT_MASK;
        msr_value.low |=  DF_OUTPUT_PANEL;
        if (mode_params->flags & VG_MODEFLAG_CRT_AND_FP)
            msr_value.low |=  DF_SIMULTANEOUS_CRT_FP;
        else
            msr_value.low &= ~DF_SIMULTANEOUS_CRT_FP;

        msr_write64 (MSR_DEVICE_GEODELX_DF, MSR_GEODELINK_CONFIG, &msr_value);

	}
    else if (mode_params->flags & VG_MODEFLAG_TVOUT)
    {
        vg3_panel_enable = 0;

        /* SET APPROPRIATE TV OUTPUT MODE */

        msr_read64 (MSR_DEVICE_GEODELX_DF, MSR_GEODELINK_CONFIG, &msr_value);

        msr_value.low &= ~DF_CONFIG_OUTPUT_MASK;
        msr_value.low |=  DF_OUTPUT_PANEL;
        if (mode_params->flags & VG_MODEFLAG_CRT_AND_FP)
            msr_value.low |=  DF_SIMULTANEOUS_CRT_FP;
        else
            msr_value.low &= ~DF_SIMULTANEOUS_CRT_FP;

        msr_write64 (MSR_DEVICE_GEODELX_DF, MSR_GEODELINK_CONFIG, &msr_value);

        /* CONFIGURE PADS FOR VOP OUTPUT */
        /* Note that the VOP clock is currently always inverted. */

        msr_value.low  = DF_DEFAULT_TV_PAD_SEL_LOW;
        msr_value.high = DF_DEFAULT_TV_PAD_SEL_HIGH;
        msr_write64 (MSR_DEVICE_GEODELX_DF, DF_MSR_PAD_SEL, &msr_value);
    }
	else
    {
		vg3_panel_enable = 0;

        /* SET OUTPUT TO CRT ONLY */

        msr_read64 (MSR_DEVICE_GEODELX_DF, MSR_GEODELINK_CONFIG, &msr_value);
        msr_value.low &= ~DF_CONFIG_OUTPUT_MASK;
        msr_value.low |=  DF_OUTPUT_CRT;
        msr_write64 (MSR_DEVICE_GEODELX_DF, MSR_GEODELINK_CONFIG, &msr_value);
    }

	/* SET UNLOCK VALUE */

	unlock = READ_REG32 (DC3_UNLOCK);
	WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);

    /*-------------------------------------------------------------------*/
    /* MAKE THE SYSTEM "SAFE"                                            */
    /* Before setting a mode, we first ensure that the system is in a    */
    /* benign quiescent state.  This involves disabling compression and  */
    /* all interrupt sources.  It also involves terminating all accesses */
    /* to memory, including video, FIFO load, VIP and the GP.            */
    /*-------------------------------------------------------------------*/

    /* DISABLE VGA */
    /* VGA *MUST* be turned off before TGEN is enabled.  If not, a condition */
    /* will result where VGA Enable is waiting for a VSync to be latched but */
    /* a VSync will not be generated until VGA is disabled.                  */
	
    temp = READ_REG32 (DC3_GENERAL_CFG) & ~DC3_GCFG_VGAE;
	
    /* DISABLE VIDEO (INCLUDING ALPHA WINDOWS) */

	WRITE_VID32 (DF_ALPHA_CONTROL_1, 0);
	WRITE_VID32 (DF_ALPHA_CONTROL_1 + 32, 0);
	WRITE_VID32 (DF_ALPHA_CONTROL_1 + 64, 0);
	
	WRITE_REG32 (DC3_GENERAL_CFG, (temp & ~DC3_GCFG_VIDE));
	temp = READ_VID32 (DF_VIDEO_CONFIG);
	WRITE_VID32 (DF_VIDEO_CONFIG, (temp & ~DF_VCFG_VID_EN));

    /* DISABLE VG INTERRUPTS */

    WRITE_REG32 (DC3_IRQ, DC3_IRQ_MASK | DC3_VSYNC_IRQ_MASK |
        DC3_IRQ_STATUS | DC3_VSYNC_IRQ_STATUS);

    /* DISABLE GENLOCK */

    genlk_ctl = READ_REG32 (DC3_GENLK_CTL);
    WRITE_REG32 (DC3_GENLK_CTL, (genlk_ctl & ~DC3_GC_GENLOCK_ENABLE));

    /* DISABLE VIP CAPTURE AND VIP INTERRUPTS */

    WRITE_VIP32 (VIP_CONTROL1, 0);
    WRITE_VIP32 (VIP_CONTROL2, 0);
    WRITE_VIP32 (VIP_INTERRUPT, VIP_ALL_INTERRUPTS | (VIP_ALL_INTERRUPTS >> 16));

    /* DISABLE COLOR KEYING */
    /* The color key mechanism should be disabled whenever a mode switch occurs. */

    temp = READ_REG32 (DC3_COLOR_KEY);
    WRITE_REG32 (DC3_COLOR_KEY, (temp & ~DC3_CLR_KEY_ENABLE));
	
    /* BLANK THE DISPLAY */
    /* Note that we never blank the panel.  Most flat panels have very long      */
    /* latency requirements when setting their power low.  Some panels require   */
    /* upwards of 500ms before VDD goes high again.  Needless to say, we are not */
    /* planning to take over one half a second inside this routine.              */

	misc   = READ_VID32 (DF_VID_MISC);
	config = READ_VID32 (DF_DISPLAY_CONFIG);
	
	WRITE_VID32 (DF_VID_MISC, (misc | DF_DAC_POWER_DOWN));
	WRITE_VID32 (DF_DISPLAY_CONFIG, (config & ~(DF_DCFG_DIS_EN   | DF_DCFG_HSYNC_EN |
		DF_DCFG_VSYNC_EN | DF_DCFG_DAC_BL_EN)));

	/* DISABLE COMPRESSION  */

	gcfg = READ_REG32 (DC3_GENERAL_CFG);
	gcfg &= ~(DC3_GCFG_CMPE | DC3_GCFG_DECE);
	WRITE_REG32 (DC3_GENERAL_CFG, gcfg);

	/* DISABLE THE TIMING GENERATOR */

	dcfg = READ_REG32 (DC3_DISPLAY_CFG);
    dcfg &= ~DC3_DCFG_TGEN;
    WRITE_REG32 (DC3_DISPLAY_CFG, dcfg);
	
	/* WAIT FOR PENDING MEMORY REQUESTS */
	
	vg_delay_milliseconds(1);

    /* DISABLE DISPLAY FIFO LOAD */

    gcfg &= ~DC3_GCFG_DFLE;
    WRITE_REG32 (DC3_GENERAL_CFG, gcfg);
	gcfg = 0;
	dcfg = 0;

    /* WAIT FOR THE GP TO BE IDLE (JUST IN CASE) */

    while (((temp = READ_GP32 (GP3_BLT_STATUS)) & GP3_BS_BLT_BUSY) ||
           !(temp & GP3_BS_CB_EMPTY))
    {
        ;
    }

	/* SET THE DOT CLOCK FREQUENCY */

    if (!(mode_params->flags & VG_MODEFLAG_EXCLUDEPLL))
    {
	    if (mode_params->flags & VG_MODEFLAG_HALFCLOCK)
		    flags = VG_PLL_DIVIDE_BY_2;
	    else if (mode_params->flags & VG_MODEFLAG_QVGA)
		    flags = VG_PLL_DIVIDE_BY_4;
	    else
		    flags = 0;

        /* ALLOW DOTREF TO BE USED AS THE PLL            */
        /* This is useful for some external TV encoders. */

        if (mode_params->flags & VG_MODEFLAG_PLL_BYPASS)
            flags |= VG_PLL_BYPASS;

        /* ALLOW THE USER TO MANUALLY ENTER THE MSR VALUE */

        if (mode_params->flags & VG_MODEFLAG_MANUAL_FREQUENCY)
            flags |= VG_PLL_MANUAL;
        if (mode_params->flags & VG_MODEFLAG_VIP_TO_DOT_CLOCK)
            flags |= VG_PLL_VIP_CLOCK;

	    vg_set_clock_frequency (mode_params->frequency, flags);
    }
	
	/* CLEAR ALL BUFFER OFFSETS */

	WRITE_REG32 (DC3_FB_ST_OFFSET,   0);
	WRITE_REG32 (DC3_CB_ST_OFFSET,   0);
	WRITE_REG32 (DC3_CURS_ST_OFFSET, 0);

    genlk_ctl = READ_REG32 (DC3_GENLK_CTL) & ~(DC3_GC_ALPHA_FLICK_ENABLE |
        DC3_GC_FLICKER_FILTER_ENABLE | DC3_GC_FLICKER_FILTER_MASK);

	/* ENABLE INTERLACING */

    if (mode_params->flags & VG_MODEFLAG_INTERLACED)
    {
        irq_ctl |= DC3_IRQFILT_INTL_EN;

        if ((mode_params->flags & VG_MODEFLAG_INT_MASK) == VG_MODEFLAG_INT_ADDRESS)
            irq_ctl |= DC3_IRQFILT_INTL_ADDR;
        else if ((mode_params->flags & VG_MODEFLAG_INT_MASK) == VG_MODEFLAG_INT_FLICKER)
        {
            genlk_ctl |= DC3_GC_FLICKER_FILTER_1_8 | DC3_GC_FLICKER_FILTER_ENABLE |
                DC3_GC_ALPHA_FLICK_ENABLE;
        }
    }
	
	WRITE_REG32 (DC3_GFX_SCALE, (vscale << 16) | (hscale & 0xFFFF));
	WRITE_REG32 (DC3_IRQ_FILT_CTL, irq_ctl);
    WRITE_REG32 (DC3_GENLK_CTL, genlk_ctl);

	/* SET LINE SIZE AND PITCH */
	/* The line size and pitch are calculated from the src_width parameter    */
	/* passed in to this routine.  All other parameters are ignored.          */
	/* The pitch is set either to a power of 2 to allow efficient             */
	/* compression or to a linear value to allow efficient memory management. */
	
    switch (bpp)
    {
        case 8:
            size = mode_params->src_width;
            line_size = starting_width;
            break;

        case 12:
        case 15:
        case 16:

            size = mode_params->src_width << 1;
            line_size = starting_width << 1;
            break;

        case 24:
        case 32:
        default:

            size = mode_params->src_width << 2;
            line_size = starting_width << 2; break;
    }
		
	/* CALCULATE DV RAM SETTINGS AND POWER OF 2 PITCH */

	pitch = 1024;
	dv_size = DC3_DV_LINE_SIZE_1024;
	
	if (size > 1024) { pitch = 2048; dv_size = DC3_DV_LINE_SIZE_2048; }
	if (size > 2048) { pitch = 4096; dv_size = DC3_DV_LINE_SIZE_4096; }
	if (size > 4096) { pitch = 8192; dv_size = DC3_DV_LINE_SIZE_8192; }
	
	/* OVERRIDE SETTINGS FOR LINEAR PITCH */

	if (mode_params->flags & VG_MODEFLAG_LINEARPITCH)
	{
        unsigned long max;
        if (pitch != size)
        {
            /* CALCULATE MAXIMUM ADDRESS (1K ALIGNED) */

            max = size * output_height;
            max = (max + 0x3FF) & 0xFFFFFC00;
            WRITE_REG32 (DC3_DV_TOP, max | DC3_DVTOP_ENABLE);

		    gcfg  |= DC3_GCFG_FDTY;
		    pitch  = size;
        }
        else
        {
            WRITE_REG32 (DC3_DV_TOP, 0);
        }
	}

	/* WRITE PITCH AND DV RAM SETTINGS */
	/* The DV RAM line length is programmed at a power of 2 boundary */
	/* in case the user wants to toggle back to a power of 2 pitch   */
	/* later.  It could happen...                                    */

	temp = READ_REG32 (DC3_DV_CTL);
	WRITE_REG32 (DC3_GFX_PITCH, pitch >> 3);
	WRITE_REG32 (DC3_DV_CTL, (temp & ~DC3_DV_LINE_SIZE_MASK) | dv_size);

	/* SET THE LINE SIZE */

	WRITE_REG32 (DC3_LINE_SIZE, (line_size + 7) >> 3);

	/* ALWAYS ENABLE VIDEO AND GRAPHICS DATA            */
	/* These bits are relics from a previous design and */
	/* should always be enabled.                        */

	dcfg |= (DC3_DCFG_VDEN | DC3_DCFG_GDEN);
	
	/* SET PIXEL FORMAT */	

	dcfg |= bpp_mask;

	/* ENABLE TIMING GENERATOR, TIM. REG. UPDATES, PALETTE BYPASS */
	/* AND VERT. INT. SELECT                                      */

    dcfg |= (unsigned long)(DC3_DCFG_TGEN | DC3_DCFG_TRUP | DC3_DCFG_PALB | DC3_DCFG_VISL);

    /* SET FIFO PRIORITIES AND DISPLAY FIFO LOAD ENABLE */
    /* Note that the bandwidth setting gets upgraded when scaling or flicker */
    /* filtering are enabled, as they require more data throughput.          */

    msr_read64 (MSR_DEVICE_GEODELX_VG, DC3_SPARE_MSR, &msr_value);
    msr_value.low &= ~(DC3_SPARE_DISABLE_CFIFO_HGO    | DC3_SPARE_VFIFO_ARB_SELECT |
                       DC3_SPARE_LOAD_WM_LPEN_MASK    | DC3_SPARE_WM_LPEN_OVRD     |
                       DC3_SPARE_DISABLE_INIT_VID_PRI | DC3_SPARE_DISABLE_VFIFO_WM);

    if ((mode_params->flags & VG_MODEFLAG_BANDWIDTHMASK) == VG_MODEFLAG_HIGH_BAND  ||
       ((mode_params->flags & VG_MODEFLAG_INTERLACED) &&
           (mode_params->flags & VG_MODEFLAG_INT_MASK) == VG_MODEFLAG_INT_FLICKER) ||
        (irq_ctl & DC3_IRQFILT_GFX_FILT_EN))
    {
        /* HIGH BANDWIDTH */
        /* Set agressive watermarks and disallow forced low priority */

        gcfg |= 0x0000BA01;
        dcfg |= 0x000EA000;
        acfg  = 0x001A0201;

        msr_value.low |= DC3_SPARE_DISABLE_CFIFO_HGO | DC3_SPARE_VFIFO_ARB_SELECT |
                         DC3_SPARE_WM_LPEN_OVRD;
    }
    else if ((mode_params->flags & VG_MODEFLAG_BANDWIDTHMASK) == VG_MODEFLAG_AVG_BAND)
    {
        /* AVERAGE BANDWIDTH */
        /* Set average watermarks and allow small regions of forced low priority. */

        gcfg |= 0x0000B601;
        dcfg |= 0x00009000;
        acfg  = 0x00160001;

        msr_value.low |= DC3_SPARE_DISABLE_CFIFO_HGO | DC3_SPARE_VFIFO_ARB_SELECT |
                         DC3_SPARE_WM_LPEN_OVRD;

        /* SET THE NUMBER OF LOW PRIORITY LINES TO 1/2 THE TOTAL AVAILABLE */

        temp  = ((READ_REG32 (DC3_V_ACTIVE_TIMING) >> 16) & 0x7FF) + 1;
        temp -=  (READ_REG32 (DC3_V_SYNC_TIMING) & 0x7FF) + 1;
	    temp >>= 1;
        if (temp > 127)
            temp = 127;

        acfg |= temp << 9;
    }
    else if ((mode_params->flags & VG_MODEFLAG_BANDWIDTHMASK) == VG_MODEFLAG_LOW_BAND)
    {
        /* LOW BANDWIDTH */
        /* Set low watermarks and allow larger regions of forced low priority. */

        gcfg |= 0x00009501;
        dcfg |= 0x00008000;
        acfg  = 0x00150001;

        msr_value.low |= DC3_SPARE_DISABLE_CFIFO_HGO | DC3_SPARE_VFIFO_ARB_SELECT |
                         DC3_SPARE_WM_LPEN_OVRD;

        /* SET THE NUMBER OF LOW PRIORITY LINES TO 3/4 THE TOTAL AVAILABLE */

        temp  = ((READ_REG32 (DC3_V_ACTIVE_TIMING) >> 16) & 0x7FF) + 1;
        temp -=  (READ_REG32 (DC3_V_SYNC_TIMING) & 0x7FF) + 1;
	    temp  = (temp * 3) >> 2;
        if (temp > 127)
            temp = 127;

        acfg |= temp << 9;
    }
    else
    {
        /* LEGACY CHARACTERISTICS */
        /* Arbitration from a single set of watermarks. */

        gcfg |= 0x0000B601;
        msr_value.low |= DC3_SPARE_DISABLE_VFIFO_WM | DC3_SPARE_DISABLE_INIT_VID_PRI;
        acfg = 0;
    }

    msr_write64 (MSR_DEVICE_GEODELX_VG, DC3_SPARE_MSR, &msr_value);

	/* ENABLE FLAT PANEL CENTERING                          */
	/* For panel modes having a resolution smaller than the */
	/* panel resolution, turn on data centering.            */

	if (mode_params->flags & VG_MODEFLAG_CENTERED)
	    dcfg |= DC3_DCFG_DCEN;

	/* COMBINE AND SET TIMING VALUES */

	temp = (mode_params->hactive - 1) | ((mode_params->htotal - 1) << 16);
	WRITE_REG32(DC3_H_ACTIVE_TIMING, temp);
	temp = (mode_params->hblankstart - 1) | ((mode_params->hblankend - 1) << 16);
	WRITE_REG32(DC3_H_BLANK_TIMING, temp);
	temp = (mode_params->hsyncstart - 1) | ((mode_params->hsyncend - 1) << 16);
	WRITE_REG32(DC3_H_SYNC_TIMING, temp);
	temp = (mode_params->vactive - 1) | ((mode_params->vtotal - 1) << 16);
	WRITE_REG32(DC3_V_ACTIVE_TIMING, temp);
	temp =	(mode_params->vblankstart - 1) | ((mode_params->vblankend - 1) << 16);
	WRITE_REG32(DC3_V_BLANK_TIMING, temp);
	temp = (mode_params->vsyncstart - 1) | ((mode_params->vsyncend - 1) << 16);
	WRITE_REG32(DC3_V_SYNC_TIMING, temp);
    temp = (mode_params->vactive_even - 1) | ((mode_params->vtotal_even - 1) << 16);
	WRITE_REG32(DC3_V_ACTIVE_EVEN, temp);
	temp =	(mode_params->vblankstart_even - 1) | ((mode_params->vblankend_even - 1) << 16);
	WRITE_REG32(DC3_V_BLANK_EVEN, temp);
	temp = (mode_params->vsyncstart_even - 1) | ((mode_params->vsyncend_even - 1) << 16);
	WRITE_REG32(DC3_V_SYNC_EVEN, temp);
	
    /* SET THE VIDEO REQUEST REGISTER */

    WRITE_VID32 (DF_VIDEO_REQUEST, 0);

	/* SET SOURCE DIMENSIONS */

	WRITE_REG32 (DC3_FB_ACTIVE, ((starting_width - 1) << 16) |
		(starting_height - 1));

	/* SET SYNC POLARITIES */

	temp = READ_VID32 (DF_DISPLAY_CONFIG);

	temp &= ~(DF_DCFG_CRT_SYNC_SKW_MASK | DF_DCFG_PWR_SEQ_DLY_MASK |
		      DF_DCFG_CRT_HSYNC_POL     | DF_DCFG_CRT_VSYNC_POL);

    temp |= (DF_DCFG_CRT_SYNC_SKW_INIT |
			 DF_DCFG_PWR_SEQ_DLY_INIT  |
             DF_DCFG_GV_PAL_BYP);

	if (mode_params->flags & VG_MODEFLAG_NEG_HSYNC)
		temp |= DF_DCFG_CRT_HSYNC_POL;
	if (mode_params->flags & VG_MODEFLAG_NEG_VSYNC)
		temp |= DF_DCFG_CRT_VSYNC_POL;

	WRITE_VID32 (DF_DISPLAY_CONFIG, temp);

    WRITE_REG32 (DC3_DISPLAY_CFG, dcfg);
    WRITE_REG32 (DC3_ARB_CFG, acfg);
	WRITE_REG32 (DC3_GENERAL_CFG, gcfg);

	/* RESTORE VALUE OF DC3_UNLOCK */

	WRITE_REG32(DC3_UNLOCK, unlock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_bpp
 *
 * This routine changes the display BPP on the fly.  It is intended only to 
 * switch between pixel depths of the same pixel size 24<->32 or 15<->16, NOT
 * between pixel depths of differing sizes 16<->32
 *---------------------------------------------------------------------------*/

int vg_set_display_bpp (int bpp)
{
    unsigned long unlock, dcfg, bpp_mask;

    switch (bpp)
	{
		case 8:  bpp_mask = DC3_DCFG_DISP_MODE_8BPP;  break;
	    case 24: bpp_mask = DC3_DCFG_DISP_MODE_24BPP; break;
		case 32: bpp_mask = DC3_DCFG_DISP_MODE_32BPP; break;
		case 12: bpp_mask = DC3_DCFG_DISP_MODE_16BPP | DC3_DCFG_12BPP; break;
		case 15: bpp_mask = DC3_DCFG_DISP_MODE_16BPP | DC3_DCFG_15BPP; break;
		case 16: bpp_mask = DC3_DCFG_DISP_MODE_16BPP | DC3_DCFG_16BPP; break;
		default: return CIM_STATUS_INVALIDPARAMS;
	}

    unlock = READ_REG32 (DC3_UNLOCK);
    dcfg = READ_REG32 (DC3_DISPLAY_CFG) & ~(DC3_DCFG_DISP_MODE_MASK | DC3_DCFG_16BPP_MODE_MASK);
    dcfg |= bpp_mask;

    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
    WRITE_REG32 (DC3_DISPLAY_CFG, dcfg);
    WRITE_REG32 (DC3_UNLOCK, unlock);

    return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_get_display_mode_index
 *
 * This routine searches the Cimarron mode table for a mode that matches the
 * input parameters.  If a match is found, the return value is the index into
 * the mode table.  If no match is found, the return value is -1.
 *---------------------------------------------------------------------------*/

int vg_get_display_mode_index (VG_QUERY_MODE *query)
{
	unsigned int mode;
	unsigned long hz_flag  = 0xFFFFFFFF;
	unsigned long bpp_flag = 0xFFFFFFFF;
    unsigned long enc_flag = 0xFFFFFFFF;
    unsigned long tv_flag  = 0;
    unsigned long interlaced = 0;
    unsigned long halfclock  = 0;
    long minimum = 0x7FFFFFFF;
    long diff;
    int match = -1;

	if (!query || !query->query_flags)
		return -1;

	if (query->query_flags & VG_QUERYFLAG_REFRESH)
	{
		/* SET FLAGS TO MATCH REFRESH RATE */

		if      (query->hz == 56)  hz_flag = VG_SUPPORTFLAG_56HZ;
		else if (query->hz == 60)  hz_flag = VG_SUPPORTFLAG_60HZ;
		else if (query->hz == 70)  hz_flag = VG_SUPPORTFLAG_70HZ;
		else if (query->hz == 72)  hz_flag = VG_SUPPORTFLAG_72HZ;
		else if (query->hz == 75)  hz_flag = VG_SUPPORTFLAG_75HZ;
		else if (query->hz == 85)  hz_flag = VG_SUPPORTFLAG_85HZ;
        else if (query->hz == 90)  hz_flag = VG_SUPPORTFLAG_90HZ;
        else if (query->hz == 100) hz_flag = VG_SUPPORTFLAG_100HZ;
		else                       hz_flag = 0;
	}
	
	if (query->query_flags & VG_QUERYFLAG_BPP)
	{
		/* SET BPP FLAGS TO LIMIT MODE SELECTION */

		if      (query->bpp == 8)  bpp_flag = VG_SUPPORTFLAG_8BPP;
		else if (query->bpp == 12) bpp_flag = VG_SUPPORTFLAG_12BPP;
		else if (query->bpp == 15) bpp_flag = VG_SUPPORTFLAG_15BPP;
		else if (query->bpp == 16) bpp_flag = VG_SUPPORTFLAG_16BPP;
		else if (query->bpp == 24) bpp_flag = VG_SUPPORTFLAG_24BPP;
		else if (query->bpp == 32) bpp_flag = VG_SUPPORTFLAG_32BPP;
		else                       bpp_flag = 0;
	}

    if (query->query_flags & VG_QUERYFLAG_ENCODER)
    {
        /* SET ENCODER FLAGS TO LIMIT MODE SELECTION */

        if      (query->encoder == VG_ENCODER_ADV7171) enc_flag = VG_SUPPORTFLAG_ADV7171;
        else if (query->encoder == VG_ENCODER_SAA7127) enc_flag = VG_SUPPORTFLAG_SAA7127;
        else if (query->encoder == VG_ENCODER_FS454)   enc_flag = VG_SUPPORTFLAG_FS454;
        else if (query->encoder == VG_ENCODER_ADV7300) enc_flag = VG_SUPPORTFLAG_ADV7300;
        else                                           enc_flag = 0;
    }

    if (query->query_flags & VG_QUERYFLAG_TVMODE)
    {
        /* SET ENCODER FLAGS TO LIMIT MODE SELECTION */

        if      (query->tvmode == VG_TVMODE_NTSC)      tv_flag = VG_SUPPORTFLAG_NTSC;
        else if (query->tvmode == VG_TVMODE_PAL)       tv_flag = VG_SUPPORTFLAG_PAL;
        else if (query->tvmode == VG_TVMODE_480P)      tv_flag = VG_SUPPORTFLAG_480P;
        else if (query->tvmode == VG_TVMODE_720P)      tv_flag = VG_SUPPORTFLAG_720P;
        else if (query->tvmode == VG_TVMODE_1080I)     tv_flag = VG_SUPPORTFLAG_1080I;
        else if (query->tvmode == VG_TVMODE_6X4_NTSC)  tv_flag = VG_SUPPORTFLAG_6X4_NTSC;
        else if (query->tvmode == VG_TVMODE_8X6_NTSC)  tv_flag = VG_SUPPORTFLAG_8X6_NTSC;
        else if (query->tvmode == VG_TVMODE_10X7_NTSC) tv_flag = VG_SUPPORTFLAG_10X7_NTSC;
        else if (query->tvmode == VG_TVMODE_6X4_PAL)   tv_flag = VG_SUPPORTFLAG_6X4_PAL;
        else if (query->tvmode == VG_TVMODE_8X6_PAL)   tv_flag = VG_SUPPORTFLAG_8X6_PAL;
        else if (query->tvmode == VG_TVMODE_10X7_PAL)  tv_flag = VG_SUPPORTFLAG_10X7_PAL;
        else                                           tv_flag = 0xFFFFFFFF;
    }

    /* SET APPROPRIATE TV AND VOP FLAGS */

    if (query->query_flags & VG_QUERYFLAG_INTERLACED)
        interlaced = query->interlaced ? VG_MODEFLAG_INTERLACED : 0;
    if (query->query_flags & VG_QUERYFLAG_HALFCLOCK)
        halfclock = query->halfclock  ? VG_MODEFLAG_HALFCLOCK  : 0;

	/* CHECK FOR INVALID REQUEST */

	if (!hz_flag || !bpp_flag || !enc_flag || tv_flag == 0xFFFFFFFF)
		return -1;

	/* LOOP THROUGH THE AVAILABLE MODES TO FIND A MATCH */

	for (mode = 0; mode < NUM_CIMARRON_DISPLAY_MODES; mode++)
	{
		if ( (!(query->query_flags & VG_QUERYFLAG_PANEL)        ||
			   (CimarronDisplayModes[mode].internal_flags & VG_SUPPORTFLAG_PANEL))          &&
			 (!(query->query_flags & VG_QUERYFLAG_TVOUT)        ||
			   (CimarronDisplayModes[mode].internal_flags & VG_SUPPORTFLAG_TVOUT))          &&
			 (!(query->query_flags & VG_QUERYFLAG_INTERLACED)   ||
			   (CimarronDisplayModes[mode].flags & VG_MODEFLAG_INTERLACED) == interlaced)   &&
             (!(query->query_flags & VG_QUERYFLAG_HALFCLOCK)    ||
			   (CimarronDisplayModes[mode].flags & VG_MODEFLAG_HALFCLOCK) == halfclock)     &&
			 (!(query->query_flags & VG_QUERYFLAG_PANELWIDTH)   ||
			   (CimarronDisplayModes[mode].panel_width == query->panel_width))              &&
			 (!(query->query_flags & VG_QUERYFLAG_PANELHEIGHT)  ||
			   (CimarronDisplayModes[mode].panel_height == query->panel_height))            &&
			 (!(query->query_flags & VG_QUERYFLAG_ACTIVEWIDTH)  ||
			   (CimarronDisplayModes[mode].hactive == query->active_width))                 &&
			 (!(query->query_flags & VG_QUERYFLAG_ACTIVEHEIGHT) ||
			   (CimarronDisplayModes[mode].vactive == query->active_height))                &&
			 (!(query->query_flags & VG_QUERYFLAG_TOTALWIDTH)   ||
			   (CimarronDisplayModes[mode].htotal == query->total_width))                   &&
			 (!(query->query_flags & VG_QUERYFLAG_TOTALHEIGHT)  ||
			   (CimarronDisplayModes[mode].vtotal == query->total_height))                  &&
			 (!(query->query_flags & VG_QUERYFLAG_BPP)          ||
			   (CimarronDisplayModes[mode].internal_flags & bpp_flag))                      &&
			 (!(query->query_flags & VG_QUERYFLAG_REFRESH)      ||
			   (CimarronDisplayModes[mode].internal_flags & hz_flag))                       &&
             (!(query->query_flags & VG_QUERYFLAG_ENCODER)      ||
			   (CimarronDisplayModes[mode].internal_flags & enc_flag))                      &&
             (!(query->query_flags & VG_QUERYFLAG_TVMODE)       ||
			  ((CimarronDisplayModes[mode].internal_flags & VG_SUPPORTFLAG_TVMODEMASK) == tv_flag)) &&
			 (!(query->query_flags & VG_QUERYFLAG_PIXELCLOCK)   ||
			   (CimarronDisplayModes[mode].frequency == query->frequency)))
		{
            /* ALLOW SEARCHING BASED ON AN APPROXIMATE PIXEL CLOCK */

            if (query->query_flags & VG_QUERYFLAG_PIXELCLOCK_APPROX)
            {
                diff = query->frequency - CimarronDisplayModes[mode].frequency;
                if (diff < 0)
                    diff = -diff;

                if (diff < minimum)
                {
                    minimum = diff;
                    match = mode;
                }
            }
            else
            {
                match = mode;
                break;
            }
		}
	}

    /* RETURN DISPLAY MODE INDEX */

	return match;
}

/*---------------------------------------------------------------------------
 * vg_get_display_mode_information
 *
 * This routine retrieves all information for a display mode contained
 * within Cimarron's mode tables.
 *---------------------------------------------------------------------------*/

int vg_get_display_mode_information (unsigned int index, VG_DISPLAY_MODE *vg_mode)
{
	if (index > NUM_CIMARRON_DISPLAY_MODES)
		return CIM_STATUS_INVALIDPARAMS;

	*vg_mode = CimarronDisplayModes[index];
	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_get_display_mode_count
 *
 * This routine retrieves the count of all predefined Cimarron modes.
 *---------------------------------------------------------------------------*/

int vg_get_display_mode_count (void)
{
	return NUM_CIMARRON_DISPLAY_MODES;
}

/*---------------------------------------------------------------------------
 * vg_get_current_display_mode
 *
 * This routine retrieves the settings for the current display.  This includes
 * any panel settings.
 *---------------------------------------------------------------------------*/

int vg_get_current_display_mode (VG_DISPLAY_MODE *current_display, int *bpp)
{
	Q_WORD msr_value;
	unsigned long active, blank, sync;
	unsigned long i, m, n, p;
	unsigned long genlk, irq, temp;
	unsigned long flags  = 0;
    unsigned long iflags = 0;

	/* READ THE CURRENT HORIZONTAL DISPLAY TIMINGS */

	active = READ_REG32 (DC3_H_ACTIVE_TIMING);
	blank  = READ_REG32 (DC3_H_BLANK_TIMING);
	sync   = READ_REG32 (DC3_H_SYNC_TIMING);

	current_display->hactive     = (active & 0xFFF) + 1;
	current_display->hblankstart = (blank  & 0xFFF) + 1;
	current_display->hsyncstart  = (sync   & 0xFFF) + 1;
	
	current_display->htotal    = ((active >> 16) & 0xFFF) + 1;
	current_display->hblankend = ((blank  >> 16) & 0xFFF) + 1;
	current_display->hsyncend  = ((sync   >> 16) & 0xFFF) + 1;

	/* READ THE CURRENT VERTICAL DISPLAY TIMINGS */

	active = READ_REG32 (DC3_V_ACTIVE_TIMING);
	blank  = READ_REG32 (DC3_V_BLANK_TIMING);
	sync   = READ_REG32 (DC3_V_SYNC_TIMING);

	current_display->vactive     = (active & 0x7FF) + 1;
	current_display->vblankstart = (blank  & 0x7FF) + 1;
	current_display->vsyncstart  = (sync   & 0x7FF) + 1;
	
	current_display->vtotal    = ((active >> 16) & 0x7FF) + 1;
	current_display->vblankend = ((blank  >> 16) & 0x7FF) + 1;
	current_display->vsyncend  = ((sync   >> 16) & 0x7FF) + 1;

    /* READ THE CURRENT EVEN FIELD VERTICAL DISPLAY TIMINGS */

	active = READ_REG32 (DC3_V_ACTIVE_EVEN);
	blank  = READ_REG32 (DC3_V_BLANK_EVEN);
	sync   = READ_REG32 (DC3_V_SYNC_EVEN);

	current_display->vactive_even     = (active & 0x7FF) + 1;
	current_display->vblankstart_even = (blank  & 0x7FF) + 1;
	current_display->vsyncstart_even  = (sync   & 0x7FF) + 1;
	
	current_display->vtotal_even    = ((active >> 16) & 0x7FF) + 1;
	current_display->vblankend_even = ((blank  >> 16) & 0x7FF) + 1;
	current_display->vsyncend_even  = ((sync   >> 16) & 0x7FF) + 1;

	/* READ THE CURRENT SOURCE DIMENSIONS                      */
	/* The DC3_FB_ACTIVE register is only used when scaling is enabled.   */
	/* As the goal of this routine is to return a structure that can be   */
	/* passed to vg_set_custom_mode to exactly recreate the current mode, */
	/* we must check the status of the scaler/filter.                     */

    genlk = READ_REG32 (DC3_GENLK_CTL);
	irq   = READ_REG32 (DC3_IRQ_FILT_CTL);
	temp  = READ_REG32 (DC3_FB_ACTIVE);
	
	current_display->src_height = (temp & 0xFFFF) + 1;
    current_display->src_width = ((temp >> 16) & 0xFFF8) + 8;
	
	/* READ THE CURRENT PANEL CONFIGURATION */
	/* We can only infer some of the panel settings based on hardware */
	/* (like when panning).  We will instead assume that the current  */
	/* mode was set using Cimarron and use the panel variables inside */
	/* Cimarron when returning the current mode information.          */

	if (vg3_panel_enable)
	{
        Q_WORD msr_value;

		flags |= VG_MODEFLAG_PANELOUT;

		current_display->panel_width  = vg3_panel_width;
		current_display->panel_height = vg3_panel_height;
		current_display->mode_width   = vg3_mode_width;
		current_display->mode_height  = vg3_mode_height;

		if (READ_REG32 (DC3_DISPLAY_CFG) & DC3_DCFG_DCEN)
			flags |= VG_MODEFLAG_CENTERED;

        msr_read64 (MSR_DEVICE_GEODELX_DF, DF_MSR_PAD_SEL, &msr_value);
        current_display->panel_tim1         = READ_VID32 (DF_VIDEO_PANEL_TIM1);
        current_display->panel_tim2         = READ_VID32 (DF_VIDEO_PANEL_TIM2);
        current_display->panel_dither_ctl   = READ_VID32 (DF_DITHER_CONTROL);
        current_display->panel_pad_sel_low  = msr_value.low;
        current_display->panel_pad_sel_high = msr_value.high;
	}
	
	/* SET MISCELLANEOUS MODE FLAGS */
	
	/* INTERLACED */

	if (irq & DC3_IRQFILT_INTL_EN)
    {
        flags |= VG_MODEFLAG_INTERLACED;
        if (irq & DC3_IRQFILT_INTL_ADDR)
            flags |= VG_MODEFLAG_INT_ADDRESS;
        else if (genlk & DC3_GC_FLICKER_FILTER_ENABLE)
            flags |= VG_MODEFLAG_INT_FLICKER;
        else
            flags |= VG_MODEFLAG_INT_LINEDOUBLE;
    }
	
	/* POLARITIES */

	temp = READ_VID32 (DF_DISPLAY_CONFIG);
	if (temp & DF_DCFG_CRT_HSYNC_POL)
		flags |= VG_MODEFLAG_NEG_HSYNC;
	if (temp & DF_DCFG_CRT_VSYNC_POL)
		flags |= VG_MODEFLAG_NEG_VSYNC;

	/* BPP */

	temp = READ_REG32 (DC3_DISPLAY_CFG) & DC3_DCFG_DISP_MODE_MASK;
	if (temp == DC3_DCFG_DISP_MODE_8BPP)
    {
		iflags |= VG_SUPPORTFLAG_8BPP;
        *bpp = 8;
    }
	else if (temp == DC3_DCFG_DISP_MODE_24BPP)
    {
        iflags |= VG_SUPPORTFLAG_24BPP;
        *bpp = 24;
    }
	else if (temp == DC3_DCFG_DISP_MODE_32BPP)
    {
        iflags |= VG_SUPPORTFLAG_32BPP;
        *bpp = 32;
    }
	else if (temp == DC3_DCFG_DISP_MODE_16BPP)
	{
		temp = READ_REG32 (DC3_DISPLAY_CFG) & DC3_DCFG_16BPP_MODE_MASK;
		if (temp == DC3_DCFG_16BPP)
        {
            iflags |= VG_SUPPORTFLAG_16BPP;
            *bpp = 16;
        }
		else if (temp == DC3_DCFG_15BPP)
        {
            iflags |= VG_SUPPORTFLAG_15BPP;
            *bpp = 15;
        }
		else if (temp == DC3_DCFG_12BPP)
        {
            iflags |= VG_SUPPORTFLAG_12BPP;
            *bpp = 12;
        }
	}

    /* TV RELATED FLAGS */

    msr_read64 (MSR_DEVICE_GEODELX_DF, DF_MSR_PAD_SEL, &msr_value);
    if (msr_value.high & DF_INVERT_VOP_CLOCK)
        flags |= VG_MODEFLAG_TVOUT;

    /* LINEAR PITCH */

    temp = (READ_REG32 (DC3_GFX_PITCH) & 0x0000FFFF) << 3;
    if (temp != 1024 && temp != 2048 && temp != 4096 && temp != 8192)
        flags |= VG_MODEFLAG_LINEARPITCH;

    /* SIMULTANEOUS CRT/FP */

    msr_read64 (MSR_DEVICE_GEODELX_DF, MSR_GEODELINK_CONFIG, &msr_value);
    if (msr_value.low & DF_SIMULTANEOUS_CRT_FP)
        flags |= VG_MODEFLAG_CRT_AND_FP;

    /* SET PLL-RELATED FLAGS */

    msr_read64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DOTPLL, &msr_value);
    if (msr_value.high & GLCP_DOTPLL_DIV4)
        flags |= VG_MODEFLAG_QVGA;
    if (msr_value.low & GLCP_DOTPLL_HALFPIX)
        flags |= VG_MODEFLAG_HALFCLOCK;

    /* SAVE THE FLAGS IN THE MODE STRUCTURE */

    current_display->internal_flags = iflags;
    current_display->flags = flags;

	/* READ PIXEL CLOCK FREQUENCY */
	/* We first search for an exact match.  If none is found, we try */
	/* a fixed point calculation and return CIM_STATUS_INEXACTMATCH. */
	
	for (i = 0; i < NUM_CIMARRON_PLL_FREQUENCIES; i++)
	{
		if (CimarronPLLFrequencies[i].pll_value == msr_value.high)
			break;
	}

	if (i == NUM_CIMARRON_PLL_FREQUENCIES)
	{
		/* ATTEMPT 16.16 CALCULATION */
		/* We assume the input frequency is 48 MHz, which is represented   */
		/* in 16.16 fixed point as 0x300000. The PLL calculation is:       */
		/*                             n + 1                               */
		/*   Fout =  48.000   *    --------------                          */
		/*                         m + 1   *  p + 1                        */

        p =  msr_value.high & 0xF;
	    n = (msr_value.high >> 4)  & 0xFF;
	    m = (msr_value.high >> 12) & 0x7;
		current_display->frequency = (0x300000 * (n + 1)) / ((p + 1) * (m + 1));

		return CIM_STATUS_INEXACTMATCH;
	}

	current_display->frequency = CimarronPLLFrequencies[i].frequency;

	/* NOW SEARCH FOR AN IDENTICAL MODE */
	/* This is just to inform the user that an exact match was found.   */
	/* With an exact match, the user can use the refresh rate flag that */
	/* is returned in the VG_DISPLAY_MODE structure.                    */

	for (i = 0; i < NUM_CIMARRON_DISPLAY_MODES; i++)
	{
		if ((CimarronDisplayModes[i].flags & current_display->flags)             &&
			 CimarronDisplayModes[i].frequency   == current_display->frequency   &&
			 CimarronDisplayModes[i].hactive     == current_display->hactive     &&
			 CimarronDisplayModes[i].hblankstart == current_display->hblankstart &&
			 CimarronDisplayModes[i].hsyncstart  == current_display->hsyncstart  &&
			 CimarronDisplayModes[i].hsyncend    == current_display->hsyncend    &&
			 CimarronDisplayModes[i].hblankend   == current_display->hblankend   &&			
			 CimarronDisplayModes[i].htotal      == current_display->htotal      &&
			 CimarronDisplayModes[i].vactive     == current_display->vactive     &&
			 CimarronDisplayModes[i].vblankstart == current_display->vblankstart &&
			 CimarronDisplayModes[i].vsyncstart  == current_display->vsyncstart  &&
			 CimarronDisplayModes[i].vsyncend    == current_display->vsyncend    &&
			 CimarronDisplayModes[i].vblankend   == current_display->vblankend   &&			
			 CimarronDisplayModes[i].vtotal      == current_display->vtotal)
		{
			break;
		}
	}

	if (i == NUM_CIMARRON_DISPLAY_MODES)
		return CIM_STATUS_INEXACTMATCH;

	current_display->internal_flags |= (CimarronDisplayModes[i].internal_flags & VG_SUPPORTFLAG_HZMASK);
	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_scaler_filter_coefficients
 *
 * This routine sets the vertical and horizontal filter coefficients for
 * graphics scaling.  If either of the input arrays is specified as NULL, a
 * set of default coeffecients will be used.
 *---------------------------------------------------------------------------*/

int vg_set_scaler_filter_coefficients (long h_taps[][5], long v_taps[][3])
{
	unsigned long irqfilt, i;
	unsigned long temp0, temp1;
    unsigned long lock;
	
	/* ENABLE ACCESS TO THE HORIZONTAL COEFFICIENTS */

	irqfilt  = READ_REG32 (DC3_IRQ_FILT_CTL);
	irqfilt |= DC3_IRQFILT_H_FILT_SEL;
	
    /* UNLOCK THE COEFFICIENT REGISTERS */

    lock = READ_REG32 (DC3_UNLOCK);
    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);

	/* WRITE COEFFICIENTS */
	/* Coefficient indexes do not auto-increment, so we must */
	/* write the address for every phase                     */

	for (i = 0; i < 256; i++)
	{
		WRITE_REG32 (DC3_IRQ_FILT_CTL, ((irqfilt & 0xFFFFFF00L) | i));

        if (!h_taps)
        {
            temp0 = CimarronHorizontalGraphicsFilter[i][0];
            temp1 = CimarronHorizontalGraphicsFilter[i][1];
        }
        else
        {
		    temp0 =  ((unsigned long)h_taps[i][0] & 0x3FF)        |
                    (((unsigned long)h_taps[i][1] & 0x3FF) << 10) |
                    (((unsigned long)h_taps[i][2] & 0x3FF) << 20);

		    temp1  = ((unsigned long)h_taps[i][3] & 0x3FF)        |
			        (((unsigned long)h_taps[i][4] & 0x3FF) << 10);
        }
        WRITE_REG32 (DC3_FILT_COEFF1, temp0);
        WRITE_REG32 (DC3_FILT_COEFF2, temp1);
	}

	/* ENABLE ACCESS TO THE VERTICAL COEFFICIENTS */

	irqfilt &= ~DC3_IRQFILT_H_FILT_SEL;

	/* WRITE COEFFICIENTS */

	for (i = 0; i < 256; i++)
	{
		WRITE_REG32 (DC3_IRQ_FILT_CTL, ((irqfilt & 0xFFFFFF00L) | i));

        if (!v_taps)
        {
            temp0 = CimarronVerticalGraphicsFilter[i];
        }
        else
        {
		    temp0 =  ((unsigned long)v_taps[i][0] & 0x3FF)        |
                    (((unsigned long)v_taps[i][1] & 0x3FF) << 10) |
                    (((unsigned long)v_taps[i][2] & 0x3FF) << 20);
        }

		WRITE_REG32 (DC3_FILT_COEFF1, temp0);
	}

    WRITE_REG32 (DC3_UNLOCK, lock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_configure_flicker_filter
 *
 * This routine updates the VG flicker filter settings when in an interlaced
 * mode.  Note that flicker filtering is enabled inside a mode set.  This routine
 * is provided to change from the default flicker filter setting of
 * 1/4, 1/2, 1/4.
 *---------------------------------------------------------------------------*/

int vg_configure_flicker_filter (unsigned long flicker_strength, int flicker_alpha)
{
    unsigned long unlock;
    unsigned long genlk_ctl;

    /* CHECK FOR VALID FLICKER SETTING */

    if (flicker_strength != VG_FLICKER_FILTER_NONE &&
        flicker_strength != VG_FLICKER_FILTER_1_16 &&
        flicker_strength != VG_FLICKER_FILTER_1_8  &&
        flicker_strength != VG_FLICKER_FILTER_1_4  &&
        flicker_strength != VG_FLICKER_FILTER_5_16)
    {
        return CIM_STATUS_INVALIDPARAMS;
    }

    unlock = READ_REG32 (DC3_UNLOCK);
    genlk_ctl  = READ_REG32 (DC3_GENLK_CTL) & ~(DC3_GC_FLICKER_FILTER_MASK | DC3_GC_ALPHA_FLICK_ENABLE);
    genlk_ctl |= flicker_strength;
    if (flicker_alpha)
        genlk_ctl |= DC3_GC_ALPHA_FLICK_ENABLE;

    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
    WRITE_REG32 (DC3_GENLK_CTL, genlk_ctl);
    WRITE_REG32 (DC3_UNLOCK, unlock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_clock_frequency
 *
 * This routine sets the frequency of the dot clock.  The input to this routine
 * is a 16.16 fraction.  If an exact match is not found, this routine will program
 * the closest available frequency and return CIM_STATUS_INEXACTMATCH.
 *---------------------------------------------------------------------------*/

int vg_set_clock_frequency (unsigned long frequency, unsigned long pll_flags)
{
	Q_WORD msr_value;
	unsigned long timeout;
	unsigned long index = 0;
	unsigned long unlock, i;
    unsigned long pll_high, pll_low;
	long diff, min = 0;

	/* FIND THE REGISTER VALUES FOR THE DESIRED FREQUENCY         */
	/* Search the table for the closest frequency (16.16 format). */
    /* This search is skipped if the user is manually specifying  */
    /* the MSR value.                                             */

    pll_low  = 0;
    if (!(pll_flags & VG_PLL_MANUAL))
    {
	    min = (long)CimarronPLLFrequencies[0].frequency - (long)frequency;
	    if (min < 0L)
		    min = -min;

	    for (i = 1; i < NUM_CIMARRON_PLL_FREQUENCIES; i++)
	    {
		    diff = (long)CimarronPLLFrequencies[i].frequency - (long)frequency;
		    if (diff < 0L)
			    diff = -diff;

		    if (diff < min)
		    {
			    min = diff;
			    index = i;
		    }
	    }

        pll_high = CimarronPLLFrequencies[index].pll_value & 0x00007FFF;
    }
    else
    {
        pll_high = frequency;
    }

    if (pll_flags & VG_PLL_DIVIDE_BY_2)
		pll_low |= GLCP_DOTPLL_HALFPIX;
	if (pll_flags & VG_PLL_DIVIDE_BY_4)
		pll_high |= GLCP_DOTPLL_DIV4;
    if (pll_flags & VG_PLL_BYPASS)
        pll_low |= GLCP_DOTPLL_BYPASS;
    if (pll_flags & VG_PLL_VIP_CLOCK)
        pll_high |= GLCP_DOTPLL_VIPCLK;

	/* VERIFY THAT WE ARE NOT WRITING WHAT IS ALREADY IN THE REGISTERS */
    /* The Dot PLL reset bit is tied to VDD for flat panels.  This can */
    /* cause a brief drop in flat panel power, which can cause serious */
    /* glitches on some panels.                                        */

	msr_read64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DOTPLL, &msr_value);

    if ((msr_value.low & GLCP_DOTPLL_LOCK) &&
       ((msr_value.low & (GLCP_DOTPLL_HALFPIX | GLCP_DOTPLL_BYPASS)) == pll_low) &&
        (msr_value.high == pll_high))
    {
        return CIM_STATUS_OK;
    }

    /* PROGRAM THE SETTINGS WITH THE RESET BIT SET */
	/* Clear the bypass bit to ensure that the programmed */
	/* M, N and P values are being used.                  */

    msr_value.high = pll_high;
	msr_value.low &= ~(GLCP_DOTPLL_BYPASS | GLCP_DOTPLL_HALFPIX);
    msr_value.low |=  (pll_low | 0x00000001);
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DOTPLL, &msr_value);

    /* WAIT FOR THE LOCK BIT */
    /* The PLL spec states that the PLL may take up to 100 us to */
    /* properly lock.  Furthermore, the lock signal is not 100%  */
    /* reliable.  To address this, we add a hefty delay followed */
    /* by a polling loop that times out after a 1000 reads.      */

    unlock = READ_REG32 (DC3_UNLOCK);
    for (timeout = 0; timeout < 1280; timeout++)
        WRITE_REG32 (DC3_UNLOCK, unlock);

    for (timeout = 0; timeout < 1000; timeout++)
    {
        msr_read64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DOTPLL, &msr_value);
        if (msr_value.low & GLCP_DOTPLL_LOCK)
            break;
    }

    /* CLEAR THE RESET BIT */

	msr_value.low &= 0xFFFFFFFE;
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DOTPLL, &msr_value);

	/* DID THE PLL SUCCESSFULLY LOCK? */

	if (!(msr_value.low & GLCP_DOTPLL_LOCK))
		return CIM_STATUS_NOLOCK;

	/* RETURN THE APPROPRIATE CODE */

	if (min == 0)
		return CIM_STATUS_OK;
	else
		return CIM_STATUS_INEXACTMATCH;
}

/*---------------------------------------------------------------------------
 * vg_set_border_color
 *
 * This routine sets the color used as the border in centered panel modes.
 *---------------------------------------------------------------------------*/

int vg_set_border_color (unsigned long border_color)
{
    unsigned long lock = READ_REG32 (DC3_UNLOCK);

    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
	WRITE_REG32 (DC3_PAL_ADDRESS, 0x104);
	WRITE_REG32 (DC3_PAL_DATA, border_color);
    WRITE_REG32 (DC3_UNLOCK, lock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_cursor_enable
 *
 * This routine enables or disables the hardware cursor.  This routine should
 * only be called after the hardware cursor has been completely configured.
 *---------------------------------------------------------------------------*/

int vg_set_cursor_enable(int enable)
{
    unsigned long unlock, gcfg;
	
	/* SET OR CLEAR CURSOR ENABLE BIT */

	unlock = READ_REG32(DC3_UNLOCK);
	gcfg   = READ_REG32(DC3_GENERAL_CFG);
	if (enable) gcfg |=   DC3_GCFG_CURE;
	else        gcfg &= ~(DC3_GCFG_CURE);	

	/* WRITE NEW REGISTER VALUE */

	WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
	WRITE_REG32 (DC3_GENERAL_CFG, gcfg);
	WRITE_REG32 (DC3_UNLOCK, unlock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_mono_cursor_colors
 *
 * This routine sets the colors of the hardware monochrome cursor.
 *---------------------------------------------------------------------------*/

int vg_set_mono_cursor_colors (unsigned long bkcolor, unsigned long fgcolor)
{
    unsigned long lock = READ_REG32 (DC3_UNLOCK);

	/* SET CURSOR COLORS */

    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
	WRITE_REG32 (DC3_PAL_ADDRESS, 0x100);
	WRITE_REG32 (DC3_PAL_DATA, bkcolor);
	WRITE_REG32 (DC3_PAL_DATA, fgcolor);
    WRITE_REG32 (DC3_UNLOCK, lock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_cursor_position
 *
 * This routine sets the position of the hardware cursor.  The cursor hotspots
 * and memory offset must have been specified in an earlier call to
 * a vg_set_cursor_shape_XX routine.  The coordinates passed to this routine
 * generally specify the focal point of the cursor, NOT the upper left coordinate of
 * the cursor pattern.  However, for operating systems that do not include a hotspot
 * the input parameters may be negative.
 *---------------------------------------------------------------------------*/

int vg_set_cursor_position (long xpos, long ypos, VG_PANNING_COORDINATES *panning)
{
	unsigned long unlock, memoffset;
    unsigned long gcfg;
	long x, xoffset;
	long y, yoffset;

	memoffset = vg3_cursor_offset;
	x = xpos - (long) vg3_x_hotspot;
	y = ypos - (long) vg3_y_hotspot;
	
	/* HANDLE NEGATIVE COORDINATES                                      */
	/* This routine supports operating systems that use negative        */
	/* coordinates, instead of positive coordinates with an appropriate */
	/* hotspot.                                                         */

	if (xpos < 0) xpos = 0;
	if (ypos < 0) ypos = 0;

	if (x < -63) return CIM_STATUS_INVALIDPARAMS;
	if (y < -63) return CIM_STATUS_INVALIDPARAMS;
	
	if (vg3_panel_enable)
	{
		if ((vg3_mode_width > vg3_panel_width) || (vg3_mode_height > vg3_panel_height))
		{
			vg_pan_desktop (xpos, ypos, panning);
			x = x - (unsigned short)vg3_delta_x;
			y = y - (unsigned short)vg3_delta_y;
		}
        else
        {
            panning->start_x = 0;
            panning->start_y = 0;
            panning->start_updated = 0;
        }
	}

	/* ADJUST OFFSETS */
	/* Cursor movement and panning work as follows:  The cursor position   */
	/* refers to where the hotspot of the cursor is located.  However, for */
	/* non-zero hotspots, the cursor buffer actually begins before the     */
	/* specified position.                                                 */

	if (x < 0) { xoffset = -x; x = 0; }
	else       { xoffset = 0;         }
	if (y < 0) { yoffset = -y; y = 0; }
	else       { yoffset = 0;         }

	if (vg3_color_cursor) memoffset += (unsigned long) yoffset * 192;
	else                  memoffset += (unsigned long) yoffset << 4;

    /* SET COLOR CURSOR BIT */

    gcfg = READ_REG32(DC3_GENERAL_CFG);
	if (vg3_color_cursor)
		gcfg |= DC3_GCFG_CLR_CUR;
	else
		gcfg &= ~DC3_GCFG_CLR_CUR;

	/* SET CURSOR POSITION */

	unlock = READ_REG32(DC3_UNLOCK);
	WRITE_REG32(DC3_UNLOCK, DC3_UNLOCK_VALUE);
	WRITE_REG32 (DC3_CURS_ST_OFFSET, memoffset);
    WRITE_REG32 (DC3_GENERAL_CFG, gcfg);
	WRITE_REG32 (DC3_CURSOR_X, (unsigned long) x |
		(((unsigned long) xoffset) << 11));
	WRITE_REG32 (DC3_CURSOR_Y, (unsigned long) y |
		(((unsigned long) yoffset) << 11));
	WRITE_REG32 (DC3_UNLOCK, unlock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_mono_cursor_shape32
 *
 * This routine loads 32x32 cursor data into the cursor buffer in graphics memory.
 * The outside of the GeodeLX cursor buffer is padded with transparency.
 *---------------------------------------------------------------------------*/

int vg_set_mono_cursor_shape32 (unsigned long memoffset, unsigned long *andmask,
	unsigned long *xormask, unsigned long x_hotspot, unsigned long y_hotspot)
{
	int i;

	/* SAVE THE CURSOR OFFSET AND HOTSPOTS                               */
	/* These are reused later when updating the cursor position, panning */
	/* and clipping the cursor pointer.                                  */

	vg3_x_hotspot     = x_hotspot;
	vg3_y_hotspot     = y_hotspot;
	vg3_cursor_offset = memoffset;
	vg3_color_cursor  = 0;

	for (i = 0; i < 32; i++)
	{
		/* EVEN QWORDS CONTAIN THE AND MASK */

		WRITE_FB32 (memoffset, 0xFFFFFFFF);
		WRITE_FB32 (memoffset + 4, andmask[i]);

		/* ODD QWORDS CONTAIN THE XOR MASK  */

		WRITE_FB32 (memoffset + 8, 0x00000000);
		WRITE_FB32 (memoffset + 12, xormask[i]);

		memoffset += 16;
	}

	/* FILL THE LOWER HALF OF THE BUFFER WITH TRANSPARENT PIXELS */

	for (i = 0; i < 32; i++)
	{
		WRITE_FB32 (memoffset,      0xFFFFFFFF);
		WRITE_FB32 (memoffset + 4,  0xFFFFFFFF);
		WRITE_FB32 (memoffset + 8,  0x00000000);
		WRITE_FB32 (memoffset + 12, 0x00000000);

		memoffset += 16;
	}

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_mono_cursor_shape64
 *
 * This routine loads 64x64 cursor data into the cursor buffer in graphics memory.
 *---------------------------------------------------------------------------*/

int vg_set_mono_cursor_shape64 (unsigned long memoffset, unsigned long *andmask,
	unsigned long *xormask, unsigned long x_hotspot, unsigned long y_hotspot)
{	
	int i;

	/* SAVE THE CURSOR OFFSET AND HOTSPOTS                               */
	/* These are reused later when updating the cursor position, panning */
	/* and clipping the cursor pointer.                                  */

	vg3_x_hotspot     = x_hotspot;
	vg3_y_hotspot     = y_hotspot;
	vg3_cursor_offset = memoffset;
	vg3_color_cursor  = 0;

	for (i = 0; i < 128; i += 2)
	{
		/* EVEN QWORDS CONTAIN THE AND MASK */
		/* We invert the dwords to prevent the calling            */
		/* application from having to think in terms of Qwords.   */
		/* The hardware data order is actually 63:0, or 31:0 of   */
		/* the second dword followed by 31:0 of the first dword.  */
		
		WRITE_FB32 (memoffset, andmask[i + 1]);
		WRITE_FB32 (memoffset + 4, andmask[i]);

		/* ODD QWORDS CONTAIN THE XOR MASK  */

		WRITE_FB32 (memoffset + 8, xormask[i + 1]);
		WRITE_FB32 (memoffset + 12, xormask[i]);

		memoffset += 16;
	}

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_color_cursor_shape
 *
 * This routine loads 8:8:8:8 cursor data into the color cursor buffer.
 *---------------------------------------------------------------------------*/

int vg_set_color_cursor_shape (unsigned long memoffset, unsigned char *data,
	unsigned long width, unsigned long height, long pitch,
	unsigned long x_hotspot, unsigned long y_hotspot)
{
	unsigned long y;

	/* SAVE THE CURSOR OFFSET AND HOTSPOTS                               */
	/* These are reused later when updating the cursor position, panning */
	/* and clipping the cursor pointer.                                  */

	vg3_x_hotspot     = x_hotspot;
	vg3_y_hotspot     = y_hotspot;
	vg3_cursor_offset = memoffset;
	vg3_color_cursor  = 1;

	/* WRITE THE CURSOR DATA */
	/* The outside edges of the color cursor are filled with transparency */
	/* The cursor buffer dimensions are 48x64.                            */

	for (y = 0; y < height; y++)
	{
		/* WRITE THE ACTIVE AND TRANSPARENT DATA */
		/* We implement this as a macro in our dedication to squeaking */
		/* every ounce of performance out of our code...               */

		WRITE_FB_STRING32 (memoffset, data, width);
		WRITE_FB_CONSTANT ((memoffset + (width << 2)), 0, (48 - width));

		/* INCREMENT PAST THE LINE */
		
		memoffset += 192;
		data += pitch;
	}

	/* WRITE THE EXTRA TRANSPARENT LINES */
	/* Write the lines in one big bulk setting. */

	WRITE_FB_CONSTANT (memoffset, 0, ((64 - height) * 48));

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_pan_desktop
 *
 * This routine sets the correct display offset based on the current cursor
 * position.
 *---------------------------------------------------------------------------*/

int vg_pan_desktop (unsigned long x, unsigned long y, VG_PANNING_COORDINATES *panning)
{
	unsigned long modeShiftPerPixel;
    unsigned long modeBytesPerScanline;
    unsigned long startAddress;

	/* TEST FOR NO-WORK */

	if (x >= vg3_delta_x && x < (vg3_panel_width  + vg3_delta_x) &&
		y >= vg3_delta_y && y < (vg3_panel_height + vg3_delta_y))
    {
        panning->start_x = vg3_delta_x;
        panning->start_y = vg3_delta_y;
        panning->start_updated = 0;
		return CIM_STATUS_OK;
    }

    if (vg3_bpp == 24) modeShiftPerPixel = 2;
    else               modeShiftPerPixel = (vg3_bpp + 7) >> 4;

	modeBytesPerScanline = (READ_REG32 (DC3_GFX_PITCH) & 0x0000FFFF) << 3;

	/* ADJUST PANNING VARIABLES WHEN CURSOR EXCEEDS BOUNDARY       */
	/* Test the boundary conditions for each coordinate and update */
	/* all variables and the starting offset accordingly.          */
	
	if (x < vg3_delta_x)
		vg3_delta_x = x;

	else if (x >= (vg3_delta_x + vg3_panel_width))
		vg3_delta_x = x - vg3_panel_width + 1;

	if (y < vg3_delta_y)
		vg3_delta_y = y;

	else if (y >= (vg3_delta_y + vg3_panel_height))
		vg3_delta_y = y - vg3_panel_height + 1;

	/* CALCULATE THE START OFFSET */

	startAddress = (vg3_delta_x << modeShiftPerPixel) + (vg3_delta_y * modeBytesPerScanline);
	
	vg_set_display_offset (startAddress);

    panning->start_updated = 1;
    panning->start_x = vg3_delta_x;
    panning->start_y = vg3_delta_y;
	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_display_offset
 *
 * This routine sets the start address of the frame buffer.  It is
 * typically used to pan across a virtual desktop (frame buffer larger than
 * the displayed screen) or to flip the display between multiple buffers.
 *---------------------------------------------------------------------------*/

int vg_set_display_offset (unsigned long address)
{
	unsigned long lock, gcfg;

	lock = READ_REG32 (DC3_UNLOCK);
	WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);

	/* DISABLE COMPRESSION */
	/* When setting a non-zero display offset, we must disable display  */
	/* compression.  We could maintain a variable and re-enable         */
	/* compression when the offset returns to zero.  However, that      */
	/* creates additional complexity for applications that perform      */
	/* graphics animation.  Re-enabling compression each time would     */
	/* be tedious and slow for such applications, implying that they    */
	/* would have to disable compression before starting the animation. */
	/* We will instead disable compression and force the user to        */
	/* re-enable compression when they are ready.                       */

	if (address != 0)
	{
		if (READ_REG32 (DC3_GENERAL_CFG) & DC3_GCFG_CMPE)
		{
			gcfg = READ_REG32 (DC3_GENERAL_CFG);
			WRITE_REG32 (DC3_GENERAL_CFG, (gcfg & ~(DC3_GCFG_CMPE | DC3_GCFG_DECE)));
		}
	}

	WRITE_REG32 (DC3_FB_ST_OFFSET, address);
	WRITE_REG32 (DC3_UNLOCK, lock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_display_pitch
 *
 * This routine sets the stride between successive lines of data in the frame
 * buffer.
 *---------------------------------------------------------------------------*/

int vg_set_display_pitch (unsigned long pitch)
{
	unsigned long temp, dvsize, dvtop, value;
	unsigned long lock = READ_REG32(DC3_UNLOCK);
	
	value = READ_REG32(DC3_GFX_PITCH) & 0xFFFF0000;
	value |= (pitch >> 3);

	/* PROGRAM THE DISPLAY PITCH */

	WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
	WRITE_REG32 (DC3_GFX_PITCH, value);

	/* SET THE COMPRESSION BEHAVIOR BASED ON THE PITCH              */
	/* Strides that are not a power of two will not work with line  */
	/* by line compression.  For these cases, we enable full-screen */
	/* compression.  In this mode, any write to the frame buffer    */
	/* region marks the entire frame as dirty.   Also, the DV line  */
    /* size must be updated when the pitch is programmed outside of */
    /* the power of 2 range specified in a mode set.                */

    if      (pitch > 4096) { dvsize = DC3_DV_LINE_SIZE_8192; }
	else if (pitch > 2048) { dvsize = DC3_DV_LINE_SIZE_4096; }
    else if (pitch > 1024) { dvsize = DC3_DV_LINE_SIZE_2048; }
    else                   { dvsize = DC3_DV_LINE_SIZE_1024; }
	
    temp = READ_REG32 (DC3_DV_CTL);
    WRITE_REG32 (DC3_DV_CTL, (temp & ~DC3_DV_LINE_SIZE_MASK) | dvsize | 0x00000001);

	value = READ_REG32 (DC3_GENERAL_CFG);

	if (pitch == 1024 || pitch == 2048 || pitch == 4096 || pitch == 8192)
    {
		value &= ~DC3_GCFG_FDTY;
        dvtop  = 0;
    }
    else
    {
		value |=  DC3_GCFG_FDTY;

        dvtop  = (READ_REG32 (DC3_FB_ACTIVE) & 0xFFF) + 1;
        dvtop  = ((dvtop * pitch) + 0x3FF) & 0xFFFFFC00;
        dvtop |= DC3_DVTOP_ENABLE;
    }

	WRITE_REG32 (DC3_GENERAL_CFG, value);
    WRITE_REG32 (DC3_DV_TOP, dvtop);
	WRITE_REG32 (DC3_UNLOCK, lock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_display_palette_entry
 *
 * This routine sets a single 8BPP palette entry in the display controller.
 *---------------------------------------------------------------------------*/

int vg_set_display_palette_entry (unsigned long index, unsigned long palette)
{
    unsigned long dcfg, unlock;

	if (index > 0xFF)
		return CIM_STATUS_INVALIDPARAMS;

    unlock = READ_REG32 (DC3_UNLOCK);
    dcfg   = READ_REG32 (DC3_DISPLAY_CFG);

    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
    WRITE_REG32 (DC3_DISPLAY_CFG, dcfg & ~DC3_DCFG_PALB);
    WRITE_REG32 (DC3_UNLOCK, unlock);

	WRITE_REG32 (DC3_PAL_ADDRESS, index);
	WRITE_REG32 (DC3_PAL_DATA, palette);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_set_display_palette
 *
 * This routine sets the entire palette in the display controller.
 * A pointer is provided to a 256 entry table of 32-bit X:R:G:B values.
 *---------------------------------------------------------------------------*/

int vg_set_display_palette (unsigned long *palette)
{
	unsigned long unlock, dcfg, i;
	WRITE_REG32 (DC3_PAL_ADDRESS, 0);
	
	if (palette)
	{
        unlock = READ_REG32 (DC3_UNLOCK);
        dcfg   = READ_REG32 (DC3_DISPLAY_CFG);

        WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
        WRITE_REG32 (DC3_DISPLAY_CFG, dcfg & ~DC3_DCFG_PALB);
        WRITE_REG32 (DC3_UNLOCK, unlock);

		for (i = 0; i < 256; i++)
		    WRITE_REG32 (DC3_PAL_DATA, palette[i]);
		
		return CIM_STATUS_OK;
	}
	return CIM_STATUS_INVALIDPARAMS;
}

/*---------------------------------------------------------------------------
 * vg_set_compression_enable
 *
 * This routine enables or disables display compression.
 *---------------------------------------------------------------------------*/

int vg_set_compression_enable (int enable)
{
    Q_WORD msr_value;
	unsigned long unlock, gcfg;
	unsigned long temp;

	unlock = READ_REG32 (DC3_UNLOCK);
	gcfg   = READ_REG32 (DC3_GENERAL_CFG);
    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);

	if (enable)
	{
		/* DO NOT ENABLE IF THE DISPLAY OFFSET IS NOT ZERO */
		
		if (READ_REG32 (DC3_FB_ST_OFFSET) & 0x0FFFFFFF)
			return CIM_STATUS_ERROR;

        /* ENABLE BIT 1 IN THE VG SPARE MSR */
        /* The bus can hang when the VG attempts to merge compression writes. */
        /* No performance is lost due to the GeodeLink QUACK features in      */
        /* GeodeLX.  We also enable the command word check for a valid        */
        /* compression header.                                                */

        msr_read64 (MSR_DEVICE_GEODELX_VG, DC3_SPARE_MSR, &msr_value);
        msr_value.low |=  DC3_SPARE_FIRST_REQ_MASK;
        msr_value.low &= ~DC3_SPARE_DISABLE_CWD_CHECK;
        msr_write64 (MSR_DEVICE_GEODELX_VG, DC3_SPARE_MSR, &msr_value);

		/* CLEAR DIRTY/VALID BITS IN MEMORY CONTROLLER */
		/* We don't want the controller to think that old lines are still     */
		/* valid.  Writing a 1 to bit 0 of the DV Control register will force */
		/* the hardware to clear all the valid bits.                          */

		temp = READ_REG32 (DC3_DV_CTL);
		WRITE_REG32 (DC3_DV_CTL, temp | 0x00000001);

		/* ENABLE COMPRESSION BITS */

		gcfg |= DC3_GCFG_CMPE | DC3_GCFG_DECE;
	}
	else
	{
		gcfg &= ~(DC3_GCFG_CMPE | DC3_GCFG_DECE);
	}

	WRITE_REG32 (DC3_GENERAL_CFG, gcfg);
	WRITE_REG32 (DC3_UNLOCK, unlock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_configure_compression
 *
 * This routine configures all aspects of display compression, including pitch,
 * size and the offset of the compression buffer.
 *---------------------------------------------------------------------------*/

int vg_configure_compression (VG_COMPRESSION_DATA *comp_data)
{
	unsigned long delta, size;
	unsigned long comp_size, unlock;

	/* CHECK FOR VALID PARAMETERS */
	/* The maximum size for the compression buffer is 544 bytes (with    */
	/* the header)  Also, the pitch cannot be less than the line size    */
	/* and the compression buffer offset must be 16-byte aligned.        */

	if (comp_data->size > 544 || comp_data->pitch < comp_data->size ||
		comp_data->compression_offset & 0x0F)
	{
		return CIM_STATUS_INVALIDPARAMS;
	}

	/* SUBTRACT 32 FROM SIZE                                           */
	/* The display controller will actually write 4 extra QWords.  So, */
	/* if we assume that "size" refers to the allocated size, we must  */
	/* subtract 32 bytes.                                              */

    comp_size = comp_data->size - 32;

	/* CALCULATE REGISTER VALUES */

	unlock = READ_REG32 (DC3_UNLOCK);
	size   = READ_REG32 (DC3_LINE_SIZE) & ~DC3_LINE_SIZE_CBLS_MASK;
	delta  = READ_REG32 (DC3_GFX_PITCH) & ~DC3_GFX_PITCH_CBP_MASK;

	size  |= ((comp_size  >> 3) + 1) << DC3_LINE_SIZE_CB_SHIFT;
	delta |= ((comp_data->pitch >> 3) << 16);

	/* WRITE COMPRESSION PARAMETERS */

	WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
	WRITE_REG32 (DC3_CB_ST_OFFSET, comp_data->compression_offset);
	WRITE_REG32 (DC3_LINE_SIZE, size);
	WRITE_REG32 (DC3_GFX_PITCH, delta);
	WRITE_REG32 (DC3_UNLOCK, unlock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_test_timing_active
 *
 * This routine checks the status of the display timing generator.
 *---------------------------------------------------------------------------*/

int vg_test_timing_active (void)
{
	if (READ_REG32 (DC3_DISPLAY_CFG) & DC3_DCFG_TGEN)
		return 1;
	
	return 0;
}

/*---------------------------------------------------------------------------
 * vg_test_vertical_active
 *
 * This routine checks if the display is currently in the middle of a frame
 * (not in the VBlank interval)
 *---------------------------------------------------------------------------*/

int vg_test_vertical_active (void)
{
	if (READ_REG32 (DC3_LINE_CNT_STATUS) & DC3_LNCNT_VNA)
		return 0;

	return 1;
}

/*---------------------------------------------------------------------------
 * vg_wait_vertical_blank
 *
 * This routine waits until the beginning of the vertical blank interval.
 * When the display is already in vertical blank, this routine will wait until
 * the beginning of the next vertical blank.
 *---------------------------------------------------------------------------*/

int vg_wait_vertical_blank(void)
{
	if (vg_test_timing_active())
	{
		while (!vg_test_vertical_active());
		while (vg_test_vertical_active());
	}
	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_test_even_field
 *
 * This routine tests the odd/even status of the current VG output field.
 *---------------------------------------------------------------------------*/

int vg_test_even_field(void)
{
	if (READ_REG32 (DC3_LINE_CNT_STATUS) & DC3_LNCNT_EVEN_FIELD)
        return 1;

    return 0;
}

/*---------------------------------------------------------------------------
 * vg_configure_line_interrupt
 *
 * This routine configures the display controller's line count interrupt.  This
 * interrupt can be used to interrupt mid-frame or to interrupt at the beginning
 * of vertical blank.
 *---------------------------------------------------------------------------*/

int vg_configure_line_interrupt (VG_INTERRUPT_PARAMS *interrupt_info)
{
	unsigned long irq_line, irq_enable;
    unsigned long lock;
	
	irq_line   = READ_REG32 (DC3_IRQ_FILT_CTL);
	irq_enable = READ_REG32 (DC3_IRQ);
    lock       = READ_REG32 (DC3_UNLOCK);
	
	irq_line = (irq_line & ~DC3_IRQFILT_LINE_MASK) | ((interrupt_info->line << 16) & DC3_IRQFILT_LINE_MASK);

	/* ENABLE OR DISABLE THE INTERRUPT */
	/* The line count is set before enabling and after disabling to  */
	/* minimize spurious interrupts.  The line count is set even     */
	/* when interrupts are disabled to allow polling-based or debug  */
	/* applications.                                                 */

    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
	if (interrupt_info->enable)
	{
		WRITE_REG32 (DC3_IRQ_FILT_CTL, irq_line);
		WRITE_REG32 (DC3_IRQ, ((irq_enable & ~DC3_IRQ_MASK) | DC3_IRQ_STATUS));
	}
	else
	{
		WRITE_REG32 (DC3_IRQ, (irq_enable | DC3_IRQ_MASK));
		WRITE_REG32 (DC3_IRQ_FILT_CTL, irq_line);
	}
    WRITE_REG32 (DC3_UNLOCK, lock);
	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_test_and_clear_interrupt
 *
 * This routine resets any pending interrupt in the video generator.  The return
 * value indicates the interrupt status prior to the reset.
 *---------------------------------------------------------------------------*/

unsigned long vg_test_and_clear_interrupt (void)
{
	unsigned long irq_enable;
    unsigned long lock;
	
	irq_enable = READ_REG32 (DC3_IRQ);
    lock       = READ_REG32 (DC3_UNLOCK);

    /* NO ACTION IF INTERRUPTS ARE MASKED */
    /* We are assuming that a driver or application will not want to receive */
    /* the status of the interrupt when it is masked.                        */

    if ((irq_enable & (DC3_IRQ_MASK | DC3_VSYNC_IRQ_MASK)) == (DC3_IRQ_MASK | DC3_VSYNC_IRQ_MASK))
        return 0;

    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
	WRITE_REG32 (DC3_IRQ, irq_enable);
    WRITE_REG32 (DC3_UNLOCK, lock);
	
    return (irq_enable & (DC3_IRQ_STATUS | DC3_VSYNC_IRQ_STATUS));
}

/*---------------------------------------------------------------------------
 * vg_test_flip_status
 *
 * This routine tests if a new display offset has been latched.
 *---------------------------------------------------------------------------*/

unsigned long vg_test_flip_status (void)
{
    return (READ_REG32 (DC3_LINE_CNT_STATUS) & DC3_LNCNT_FLIP);
}

/*---------------------------------------------------------------------------
 * vg_save_state
 *
 * This routine saves all persistent VG state information.
 *---------------------------------------------------------------------------*/

int vg_save_state (VG_SAVE_RESTORE *vg_state)
{
    Q_WORD msr_value;
    unsigned long irqfilt;
    unsigned long offset, i;
    unsigned long lock;

    /* READ ALL CURRENT REGISTER SETTINGS */

    vg_state->unlock                = READ_REG32 (DC3_UNLOCK);
    vg_state->gcfg                  = READ_REG32 (DC3_GENERAL_CFG);
    vg_state->dcfg                  = READ_REG32 (DC3_DISPLAY_CFG);
    vg_state->arb_cfg               = READ_REG32 (DC3_ARB_CFG);
    vg_state->fb_offset             = READ_REG32 (DC3_FB_ST_OFFSET);
    vg_state->cb_offset             = READ_REG32 (DC3_CB_ST_OFFSET);
    vg_state->cursor_offset         = READ_REG32 (DC3_CURS_ST_OFFSET);
    vg_state->video_y_offset        = READ_REG32 (DC3_VID_Y_ST_OFFSET);
    vg_state->video_u_offset        = READ_REG32 (DC3_VID_U_ST_OFFSET);
    vg_state->video_v_offset        = READ_REG32 (DC3_VID_V_ST_OFFSET);
    vg_state->dv_top                = READ_REG32 (DC3_DV_TOP);
    vg_state->line_size             = READ_REG32 (DC3_LINE_SIZE);
    vg_state->gfx_pitch             = READ_REG32 (DC3_GFX_PITCH);
    vg_state->video_yuv_pitch       = READ_REG32 (DC3_VID_YUV_PITCH);
    vg_state->h_active              = READ_REG32 (DC3_H_ACTIVE_TIMING);
    vg_state->h_blank               = READ_REG32 (DC3_H_BLANK_TIMING);
    vg_state->h_sync                = READ_REG32 (DC3_H_SYNC_TIMING);
    vg_state->v_active              = READ_REG32 (DC3_V_ACTIVE_TIMING);
    vg_state->v_blank               = READ_REG32 (DC3_V_BLANK_TIMING);
    vg_state->v_sync                = READ_REG32 (DC3_V_SYNC_TIMING);
    vg_state->fb_active             = READ_REG32 (DC3_FB_ACTIVE);
    vg_state->cursor_x              = READ_REG32 (DC3_CURSOR_X);
    vg_state->cursor_y              = READ_REG32 (DC3_CURSOR_Y);
    vg_state->vid_ds_delta          = READ_REG32 (DC3_VID_DS_DELTA);
    vg_state->fb_base               = READ_REG32 (DC3_PHY_MEM_OFFSET);
    vg_state->dv_ctl                = READ_REG32 (DC3_DV_CTL);
    vg_state->gfx_scale             = READ_REG32 (DC3_GFX_SCALE);
    vg_state->irq_ctl               = READ_REG32 (DC3_IRQ_FILT_CTL);
    vg_state->vbi_even_ctl          = READ_REG32 (DC3_VBI_EVEN_CTL);
    vg_state->vbi_odd_ctl           = READ_REG32 (DC3_VBI_ODD_CTL);
    vg_state->vbi_hor_ctl           = READ_REG32 (DC3_VBI_HOR);
    vg_state->vbi_odd_line_enable   = READ_REG32 (DC3_VBI_LN_ODD);
    vg_state->vbi_even_line_enable  = READ_REG32 (DC3_VBI_LN_EVEN);
    vg_state->vbi_pitch             = READ_REG32 (DC3_VBI_PITCH);
    vg_state->color_key             = READ_REG32 (DC3_COLOR_KEY);
    vg_state->color_key_mask        = READ_REG32 (DC3_COLOR_MASK);
    vg_state->color_key_x           = READ_REG32 (DC3_CLR_KEY_X);
    vg_state->color_key_y           = READ_REG32 (DC3_CLR_KEY_Y);
    vg_state->irq                   = READ_REG32 (DC3_IRQ);
    vg_state->genlk_ctl             = READ_REG32 (DC3_GENLK_CTL);
    vg_state->vid_y_even_offset     = READ_REG32 (DC3_VID_EVEN_Y_ST_OFFSET);
    vg_state->vid_u_even_offset     = READ_REG32 (DC3_VID_EVEN_U_ST_OFFSET);
    vg_state->vid_v_even_offset     = READ_REG32 (DC3_VID_EVEN_V_ST_OFFSET);
    vg_state->vactive_even          = READ_REG32 (DC3_V_ACTIVE_EVEN);
    vg_state->vblank_even           = READ_REG32 (DC3_V_BLANK_EVEN);
    vg_state->vsync_even            = READ_REG32 (DC3_V_SYNC_EVEN);

    /* READ THE CURRENT PALETTE */

    lock = READ_REG32 (DC3_UNLOCK);
    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
    WRITE_REG32 (DC3_PAL_ADDRESS, 0);
    for (i = 0; i < 261; i++)
        vg_state->palette[i] = READ_REG32 (DC3_PAL_DATA);

    /* READ THE CURRENT FILTER COEFFICIENTS */

	/* ENABLE ACCESS TO THE HORIZONTAL COEFFICIENTS */

	irqfilt  = READ_REG32 (DC3_IRQ_FILT_CTL);
	irqfilt |= DC3_IRQFILT_H_FILT_SEL;
	
	/* READ HORIZONTAL COEFFICIENTS */

    for (i = 0; i < 256; i++)
	{
		WRITE_REG32 (DC3_IRQ_FILT_CTL, ((irqfilt & 0xFFFFFF00L) | i));

        vg_state->h_coeff[(i << 1)]     = READ_REG32 (DC3_FILT_COEFF1);
        vg_state->h_coeff[(i << 1) + 1] = READ_REG32 (DC3_FILT_COEFF2);
	}

	/* ENABLE ACCESS TO THE VERTICAL COEFFICIENTS */

	irqfilt &= ~DC3_IRQFILT_H_FILT_SEL;

	/* READ COEFFICIENTS */

	for (i = 0; i < 256; i++)
	{
		WRITE_REG32 (DC3_IRQ_FILT_CTL, ((irqfilt & 0xFFFFFF00L) | i));

        vg_state->v_coeff[i] = READ_REG32 (DC3_FILT_COEFF1);
	}

    /* READ THE CURSOR DATA */

    offset = READ_REG32 (DC3_CURS_ST_OFFSET) & 0x0FFFFFFF;
    for (i = 0; i < 3072; i++)
        vg_state->cursor_data[i] = READ_FB32 (offset + (i << 2));

    /* READ THE CURRENT PLL */

    msr_read64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DOTPLL, &msr_value);

    vg_state->pll_flags = 0;
    for (i = 0; i < NUM_CIMARRON_PLL_FREQUENCIES; i++)
    {
        if (CimarronPLLFrequencies[i].pll_value == (msr_value.high & 0x7FFF))
        {
            vg_state->dot_pll = CimarronPLLFrequencies[i].frequency;
            break;
        }
    }

    if (i == NUM_CIMARRON_PLL_FREQUENCIES)
    {
	    /* NO MATCH */
        /* Enter the frequency as a manual frequency. */

	    vg_state->dot_pll = msr_value.high;
        vg_state->pll_flags |= VG_PLL_MANUAL;
    }
    if (msr_value.low & GLCP_DOTPLL_HALFPIX)
    	vg_state->pll_flags |= VG_PLL_DIVIDE_BY_2;
    if (msr_value.low & GLCP_DOTPLL_BYPASS)
        vg_state->pll_flags |= VG_PLL_BYPASS;
    if (msr_value.high & GLCP_DOTPLL_DIV4)
        vg_state->pll_flags |= VG_PLL_DIVIDE_BY_4;
    if (msr_value.high & GLCP_DOTPLL_VIPCLK)
        vg_state->pll_flags |= VG_PLL_VIP_CLOCK;

    /* READ ALL VG MSRS */

    msr_read64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_CAP,    &(vg_state->msr_cap));
    msr_read64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_CONFIG, &(vg_state->msr_config));
    msr_read64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_SMI,    &(vg_state->msr_smi));
    msr_read64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_ERROR,  &(vg_state->msr_error));
    msr_read64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_PM,     &(vg_state->msr_pm));
    msr_read64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_DIAG,   &(vg_state->msr_diag));
    msr_read64 (MSR_DEVICE_GEODELX_VG, DC3_SPARE_MSR,        &(vg_state->msr_spare));
    msr_read64 (MSR_DEVICE_GEODELX_VG, DC3_RAM_CTL,          &(vg_state->msr_ram_ctl));

    WRITE_REG32 (DC3_UNLOCK, lock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_restore_state
 *
 * This routine restores all persistent VG state information.
 *---------------------------------------------------------------------------*/

int vg_restore_state (VG_SAVE_RESTORE *vg_state)
{
    unsigned long irqfilt, i;
	unsigned long memoffset;

    /* TEMPORARILY UNLOCK ALL REGISTERS */

    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);

    /* RESTORE THE FRAME BUFFER OFFSET */

    WRITE_REG32 (DC3_PHY_MEM_OFFSET, vg_state->fb_base);

    /* BLANK GCFG AND DCFG */

    WRITE_REG32 (DC3_GENERAL_CFG, 0);
    WRITE_REG32 (DC3_DISPLAY_CFG, 0);

    /* RESTORE ALL REGISTERS */

    WRITE_REG32 (DC3_ARB_CFG,               vg_state->arb_cfg);
    WRITE_REG32 (DC3_FB_ST_OFFSET,          vg_state->fb_offset);
    WRITE_REG32 (DC3_CB_ST_OFFSET,          vg_state->cb_offset);
    WRITE_REG32 (DC3_CURS_ST_OFFSET,        vg_state->cursor_offset);
    WRITE_REG32 (DC3_VID_Y_ST_OFFSET,       vg_state->video_y_offset);
    WRITE_REG32 (DC3_VID_U_ST_OFFSET,       vg_state->video_u_offset);
    WRITE_REG32 (DC3_VID_V_ST_OFFSET,       vg_state->video_v_offset);
    WRITE_REG32 (DC3_DV_TOP,                vg_state->dv_top);
    WRITE_REG32 (DC3_LINE_SIZE,             vg_state->line_size);
    WRITE_REG32 (DC3_GFX_PITCH,             vg_state->gfx_pitch);
    WRITE_REG32 (DC3_VID_YUV_PITCH,         vg_state->video_yuv_pitch);
    WRITE_REG32 (DC3_H_ACTIVE_TIMING,       vg_state->h_active);
    WRITE_REG32 (DC3_H_BLANK_TIMING,        vg_state->h_blank);
    WRITE_REG32 (DC3_H_SYNC_TIMING,         vg_state->h_sync);
    WRITE_REG32 (DC3_V_ACTIVE_TIMING,       vg_state->v_active);
    WRITE_REG32 (DC3_V_BLANK_TIMING,        vg_state->v_blank);
    WRITE_REG32 (DC3_V_SYNC_TIMING,         vg_state->v_sync);
    WRITE_REG32 (DC3_FB_ACTIVE,             vg_state->fb_active);
    WRITE_REG32 (DC3_CURSOR_X,              vg_state->cursor_x);
    WRITE_REG32 (DC3_CURSOR_Y,              vg_state->cursor_y);
    WRITE_REG32 (DC3_VID_DS_DELTA,          vg_state->vid_ds_delta);
    WRITE_REG32 (DC3_PHY_MEM_OFFSET,        vg_state->fb_base);
    WRITE_REG32 (DC3_DV_CTL,                vg_state->dv_ctl | 0x00000001);
    WRITE_REG32 (DC3_GFX_SCALE,             vg_state->gfx_scale);
    WRITE_REG32 (DC3_IRQ_FILT_CTL,          vg_state->irq_ctl);
    WRITE_REG32 (DC3_VBI_EVEN_CTL,          vg_state->vbi_even_ctl);
    WRITE_REG32 (DC3_VBI_ODD_CTL,           vg_state->vbi_odd_ctl);
    WRITE_REG32 (DC3_VBI_HOR,               vg_state->vbi_hor_ctl);
    WRITE_REG32 (DC3_VBI_LN_ODD,            vg_state->vbi_odd_line_enable);
    WRITE_REG32 (DC3_VBI_LN_EVEN,           vg_state->vbi_even_line_enable);
    WRITE_REG32 (DC3_VBI_PITCH,             vg_state->vbi_pitch);
    WRITE_REG32 (DC3_COLOR_KEY,             vg_state->color_key);
    WRITE_REG32 (DC3_COLOR_MASK,            vg_state->color_key_mask);
    WRITE_REG32 (DC3_CLR_KEY_X,             vg_state->color_key_x);
    WRITE_REG32 (DC3_CLR_KEY_Y,             vg_state->color_key_y);
    WRITE_REG32 (DC3_IRQ,                   vg_state->irq);
    WRITE_REG32 (DC3_GENLK_CTL,             vg_state->genlk_ctl);
    WRITE_REG32 (DC3_VID_EVEN_Y_ST_OFFSET,  vg_state->vid_y_even_offset);
    WRITE_REG32 (DC3_VID_EVEN_U_ST_OFFSET,  vg_state->vid_u_even_offset);
    WRITE_REG32 (DC3_VID_EVEN_V_ST_OFFSET,  vg_state->vid_v_even_offset);
    WRITE_REG32 (DC3_V_ACTIVE_EVEN,         vg_state->vactive_even);
    WRITE_REG32 (DC3_V_BLANK_EVEN,          vg_state->vblank_even);
    WRITE_REG32 (DC3_V_SYNC_EVEN,           vg_state->vsync_even);

    /* RESTORE THE PALETTE */

    WRITE_REG32 (DC3_PAL_ADDRESS, 0);
    for (i = 0; i < 261; i++)
        WRITE_REG32 (DC3_PAL_DATA, vg_state->palette[i]);

    /* RESTORE THE HORIZONTAL FILTER COEFFICIENTS */

	irqfilt  = READ_REG32 (DC3_IRQ_FILT_CTL);
	irqfilt |= DC3_IRQFILT_H_FILT_SEL;

    for (i = 0; i < 256; i++)
	{
		WRITE_REG32 (DC3_IRQ_FILT_CTL, ((irqfilt & 0xFFFFFF00L) | i));
        WRITE_REG32 (DC3_FILT_COEFF1, vg_state->h_coeff[(i << 1)]);
        WRITE_REG32 (DC3_FILT_COEFF2, vg_state->h_coeff[(i << 1) + 1]);
	}

	/* RESTORE VERTICAL COEFFICIENTS */

	irqfilt &= ~DC3_IRQFILT_H_FILT_SEL;

	for (i = 0; i < 256; i++)
	{
		WRITE_REG32 (DC3_IRQ_FILT_CTL, ((irqfilt & 0xFFFFFF00L) | i));
        WRITE_REG32 (DC3_FILT_COEFF1, vg_state->v_coeff[i]);
	}

    /* RESTORE THE CURSOR DATA */

    memoffset = READ_REG32 (DC3_CURS_ST_OFFSET) & 0x0FFFFFFF;
    WRITE_FB_STRING32 (memoffset, (unsigned char *)&(vg_state->cursor_data[0]), 3072);

    /* RESTORE THE PLL */
    /* Use a common routine to use common code to poll for lock bit */

    vg_set_clock_frequency (vg_state->dot_pll, vg_state->pll_flags);

    /* RESTORE ALL VG MSRS */

    msr_write64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_CAP,    &(vg_state->msr_cap));
    msr_write64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_CONFIG, &(vg_state->msr_config));
    msr_write64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_SMI,    &(vg_state->msr_smi));
    msr_write64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_ERROR,  &(vg_state->msr_error));
    msr_write64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_PM,     &(vg_state->msr_pm));
    msr_write64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_DIAG,   &(vg_state->msr_diag));
    msr_write64 (MSR_DEVICE_GEODELX_VG, DC3_SPARE_MSR,        &(vg_state->msr_spare));
    msr_write64 (MSR_DEVICE_GEODELX_VG, DC3_RAM_CTL,          &(vg_state->msr_ram_ctl));

    /* NOW RESTORE GCFG AND DCFG */

    WRITE_REG32 (DC3_DISPLAY_CFG, vg_state->dcfg);
	WRITE_REG32 (DC3_GENERAL_CFG, vg_state->gcfg);

    /* FINALLY RESTORE UNLOCK */

    WRITE_REG32 (DC3_UNLOCK, vg_state->unlock);

	return CIM_STATUS_OK;
}

/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * CIMARRON VG READ ROUTINES
 * These routines are included for use in diagnostics or when debugging.  They
 * can be optionally excluded from a project.
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

#if CIMARRON_INCLUDE_VG_READ_ROUTINES

/*---------------------------------------------------------------------------
 * vg_read_graphics_crc
 *
 * This routine reads the Cyclic Redundancy Check (CRC) value for the graphics
 * frame.
 *---------------------------------------------------------------------------*/

unsigned long vg_read_graphics_crc (int crc_source)
{
    unsigned long gcfg, unlock;
	unsigned long crc, vbi_even;
    unsigned long interlaced;
    unsigned long line, field;

	if (!(READ_REG32 (DC3_DISPLAY_CFG) & DC3_DCFG_TGEN))
		return 0xFFFFFFFF;

	unlock   = READ_REG32 (DC3_UNLOCK);
	gcfg     = READ_REG32 (DC3_GENERAL_CFG);
    vbi_even = READ_REG32 (DC3_VBI_EVEN_CTL);

    vbi_even &= ~DC3_VBI_EVEN_ENABLE_CRC;

	gcfg |=   DC3_GCFG_SGRE | DC3_GCFG_CRC_MODE;
	gcfg &= ~(DC3_GCFG_SGFR | DC3_GCFG_SIG_SEL | DC3_GCFG_FILT_SIG_SEL);

    switch (crc_source)
    {
        case VG_CRC_SOURCE_PREFILTER_EVEN:
        case VG_CRC_SOURCE_PREFILTER:        gcfg |= DC3_GCFG_SIG_SEL;      break;
        case VG_CRC_SOURCE_PREFLICKER:
        case VG_CRC_SOURCE_PREFLICKER_EVEN:  gcfg |= DC3_GCFG_FILT_SIG_SEL; break;
        case VG_CRC_SOURCE_POSTFLICKER:
        case VG_CRC_SOURCE_POSTFLICKER_EVEN: /* NO WORK */                  break;

        default:
            return 0xFFFFFFFF;
    }

    if (crc_source & VG_CRC_SOURCE_EVEN) field = 0;
    else                                 field = DC3_LNCNT_EVEN_FIELD;

    if ((interlaced = (READ_REG32 (DC3_IRQ_FILT_CTL) & DC3_IRQFILT_INTL_EN)))
    {
        /* WAIT FOR THE BEGINNING OF THE FIELD (LINE 1-5) */
        /* Note that we wait for the field to be odd when CRCing the even */
        /* field and vice versa.  This is because the CRC will not begin  */
        /* until the following field.                                     */

        do
        {
            line = READ_REG32 (DC3_LINE_CNT_STATUS);
        } while ((line & DC3_LNCNT_EVEN_FIELD) != field    ||
                ((line & DC3_LNCNT_V_LINE_CNT) >> 16) < 10 ||
                ((line & DC3_LNCNT_V_LINE_CNT) >> 16) > 15);
    }
    else
    {
        /* NON-INTERLACED - EVEN FIELD CRCS ARE INVALID */

        if (crc_source & VG_CRC_SOURCE_EVEN)
            return 0xFFFFFFFF;
    }

	WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
    WRITE_REG32 (DC3_VBI_EVEN_CTL, vbi_even);
	WRITE_REG32 (DC3_GENERAL_CFG, gcfg & ~DC3_GCFG_SIGE);
    WRITE_REG32 (DC3_GENERAL_CFG, gcfg |  DC3_GCFG_SIGE);

	/* WAIT FOR THE CRC TO BE COMPLETED */

	while (!(READ_REG32 (DC3_LINE_CNT_STATUS) & DC3_LNCNT_SIGC))
		;

	/* READ THE COMPLETED CRC */

	crc = READ_REG32 (DC3_PAL_DATA);

	/* RESTORE THE PALETTE SETTINGS */

	gcfg &= ~DC3_GCFG_SGRE;
	WRITE_REG32 (DC3_GENERAL_CFG, gcfg);
	WRITE_REG32 (DC3_UNLOCK, unlock);

	return crc;
}

/*---------------------------------------------------------------------------
 * vg_read_window_crc
 *
 * This routine reads the Cyclic Redundancy Check (CRC) value for a sub-section
 * of the frame.
 *---------------------------------------------------------------------------*/

unsigned long vg_read_window_crc (int crc_source, unsigned long x, unsigned long y,
    unsigned long width, unsigned long height)
{
    Q_WORD msr_value;
	unsigned long crc = 0;
    unsigned long hactive, hblankstart;
    unsigned long htotal,  hblankend;
    unsigned long line, field;
    unsigned long diag;

    hactive     = ((READ_REG32 (DC3_H_ACTIVE_TIMING)) & 0xFFF) + 1;
    hblankstart = ((READ_REG32 (DC3_H_BLANK_TIMING))  & 0xFFF) + 1;
    htotal      = ((READ_REG32 (DC3_H_ACTIVE_TIMING) >> 16) & 0xFFF) + 1;
    hblankend   = ((READ_REG32 (DC3_H_BLANK_TIMING)  >> 16) & 0xFFF) + 1;

    /* TIMINGS MUST BE ACTIVE */

	if (!(READ_REG32 (DC3_DISPLAY_CFG) & DC3_DCFG_TGEN))
		return 0xFFFFFFFF;

    /* DISABLE GLCP ACTIONS */

    msr_value.low  = 0;
    msr_value.high = 0;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DIAGCTL, &msr_value);

    if ((x == 0 && width == 1) || x == 1)
    {
        /* SPECIAL CASE FOR X == 0 */
        /* The comparator output is a clock late in the MCP, so we cannot */
        /* easily catch the first pixel.  If the first pixel is desired,  */
        /* we will insert a special state machine to CRC just the first   */
        /* pixel.                                                         */

        /* N2 - DISPE HIGH AND Y == 1 */
        /* Goto state YState = 2      */

        msr_value.high = 0x00000002;
        msr_value.low  = 0x00000C00;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_SETN0CTL + 2, &msr_value);

        /* M3 - DISPE HIGH AND Y == 0 */
        /* Goto YState = 1            */

        msr_value.high = 0x00000002;
        msr_value.low  = 0x00000A00;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_SETM0CTL + 3, &msr_value);

        /* N3 - DISPE LOW  */
        /* Goto YState = 0 */

        msr_value.high = 0x00080000;
        msr_value.low  = 0x00000000;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_SETN0CTL + 3, &msr_value);

        /* Y0 -> Y1 (SET M3) */

        msr_value.high = 0x00000000;
        msr_value.low  = 0x0000C000;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 18, &msr_value);

        /* Y1 -> Y0 (SET N3) */

        msr_value.low = 0x0000A000;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 17, &msr_value);

        /* Y1 -> Y2 (SET N2) */

        msr_value.low = 0x00000A00;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 19, &msr_value);

        /* N5 (XSTATE = 10 && CMP2 <= V. COUNTER <= CMP3) && DISPE && Y == 0 */
        /* CRC into REGB                                                     */

        msr_value.high = 0x00000002;
        msr_value.low  = 0x10800B20;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_SETN0CTL + 5, &msr_value);

        /* N6 (XSTATE = 10 && CMP2 <= V. COUNTER <= CMP3) && DISPE && Y == 1 */
        /* CRC into REGB                                                     */

        msr_value.high = 0x00000002;
        msr_value.low  = 0x10800D20;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_SETN0CTL + 6, &msr_value);
    }

    /* M4 (XSTATE = 00 AND VSYNC HIGH) */
    /* Goto state 01                   */
    /* Note: VSync = H3A               */

    msr_value.high = 0x00000001;
    msr_value.low  = 0x000000A0;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_SETM0CTL + 4, &msr_value);

    /* N0 (XSTATE = 01 AND VSYNC LOW) */
    /* Goto state 02                  */
    /* Note: VSync low = H3B          */

    msr_value.high = 0x00040000;
    msr_value.low  = 0x000000C0;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_SETN0CTL, &msr_value);

    /* M5 (XSTATE = 10 AND VSYNC HIGH) */
    /* Goto state 11                   */

    msr_value.high = 0x00000001;
    msr_value.low  = 0x00000120;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_SETM0CTL + 5, &msr_value);

    /* N1 (XSTATE = 10 and DISPE HIGH) */
    /* Increment H. Counter           */
    /* Note: DispE = H4               */

    msr_value.high = 0x00000002;
    msr_value.low  = 0x00000120;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_SETN0CTL + 1, &msr_value);

    /* M0 (XSTATE = 10 and H. COUNTER == LIMIT)  */
    /* Clear H. Counter and increment V. Counter */

    msr_value.high = 0x00000000;
    msr_value.low  = 0x00000122;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_SETM0CTL, &msr_value);

    /* N4 (XSTATE = 10 && CMP0 <= H. COUNTER <= CMP1 && CMP2 <= V. COUNTER <= CMP3) && DISPE */
    /* CRC into REGB                                                                         */

    msr_value.high = 0x00000002;
    msr_value.low  = 0x10C20120;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_SETN0CTL + 4, &msr_value);

    /* COMPARATOR 0 VALUE                                         */
    /* We subtract 1 to account for a pipeline delay in the GLCP. */
    /* When the x coordinate is 0, we must play a special game.   */
    /* If the width is exactly 1, we will set up a state machine  */
    /* to only CRC the first pixel.  Otherwise, we will set it    */
    /* as an OR combination of a state that CRCs the first pixel  */
    /* and a state that CRCs 1 clock delayed width (width - 1)    */

    msr_value.high = 0;
    if (x > 1) msr_value.low = (x - 1) & 0xFFFF;
    else       msr_value.low = x;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_CMPVAL0, &msr_value);

    /* COMPARATOR 1 VALUE */

    if ((x == 0 || x == 1) && width > 1) msr_value.low += width - 2;
    else                                 msr_value.low += width - 1;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_CMPVAL0 + 2, &msr_value);

    /* COMPARATOR 2 VALUE */

    msr_value.low = y << 16;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_CMPVAL0 + 4, &msr_value);

    /* COMPARATOR 3 VALUE */

    msr_value.low += (height - 1) << 16;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_CMPVAL0 + 6, &msr_value);

    /* COMPARATOR MASKS */
    /* Comparators 0 and 1 refer to lower 16 bits of RegB */

    msr_value.low = 0x0000FFFF;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_CMPMASK0, &msr_value);
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_CMPMASK0 + 2, &msr_value);

    /* Comparators 2 and 3 refer to upper 16 bits of RegB */

    msr_value.low = 0xFFFF0000;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_CMPMASK0 + 4, &msr_value);
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_CMPMASK0 + 6, &msr_value);

    /* SET REGB MASK                                               */
    /* We set the mask such that all all 32 bits of data are CRCed */

    msr_value.low = 0xFFFFFFFF;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_REGBMASK, &msr_value);

    /* ACTIONS */

    /* STATE 00->01 (SET 4M) */

    msr_value.low = 0x000C0000;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 14, &msr_value);

    /* STATE 01->10 (SET 0N) */

    msr_value.low = 0x0000000A;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 15, &msr_value);

    /* STATE 10->11 (SET 5M) */

    msr_value.low = 0x00C00000;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 16, &msr_value);

    /* CLEAR REGA WHEN TRANSITIONING TO STATE 10                 */
    /* Do not clear RegB as the initial value must be 0x00000001 */

    msr_value.low = 0x0000000A;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0, &msr_value);

    /* REGISTER ACTION 1 */
    /* CRC into RegB if cmp0 <= h.counter <= cmp1 && cmp2 <= v. counter < cmp3 && 7 xstate = 10 */
    /* Increment h.counter if xstate = 10 and HSync is low.                                     */

    msr_value.low = 0x000A00A0;
    if (x == 0 && width == 1)
        msr_value.low  = 0x00A000A0;
    else if (x == 1 && width == 1)
        msr_value.low = 0x0A0000A0;
    else if (x == 1 && width > 1)
        msr_value.low |= 0x0A000000;

    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 1, &msr_value);

    /* REGISTER ACTION 2            */
    /* Increment V. Counter in REGA */

    msr_value.low = 0x0000000C;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 2, &msr_value);

    /* SET REGB TO 0x00000001 */

    msr_value.low = 0x00000001;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_REGB, &msr_value);

    /* SET XSTATE TO 0 */

    msr_value.low = 0x00000000;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_XSTATE, &msr_value);

    /* SET YSTATE TO 0 */

    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_YSTATE, &msr_value);

    /* CLEAR ALL OTHER ACTIONS */
	/* This prevents side-effects from previous accesses to the GLCP */
	/* debug logic.                                                  */

	msr_value.low  = 0x00000000;
	msr_value.high = 0x00000000;
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 3, &msr_value);
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 4, &msr_value);
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 5, &msr_value);
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 6, &msr_value);
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 7, &msr_value);
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 8, &msr_value);
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 9, &msr_value);
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 10, &msr_value);
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 11, &msr_value);
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 12, &msr_value);
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 13, &msr_value);	
	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_ACTION0 + 20, &msr_value);

    /* SET DIAG SETTINGS BASED ON DESIRED CRC */

    if (crc_source == VG_CRC_SOURCE_POSTFLICKER || crc_source == VG_CRC_SOURCE_POSTFLICKER_EVEN)
    {
        diag = 0x80808086;

        /* ENABLE HW CLOCK GATING AND SET GLCP CLOCK TO DOT CLOCK */

	    msr_value.high = 0;
	    msr_value.low  = 5;
	    msr_write64 (MSR_DEVICE_GEODELX_GLCP, MSR_GEODELINK_PM, &msr_value);
	    msr_value.low = 0;
	    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DBGCLKCTL, &msr_value);
	    msr_value.low = 3;
	    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DBGCLKCTL, &msr_value);

        /* SET REGA LIMITS                              */
        /* Lower counter uses pixels/line               */
        /* Upper counter is 0xFFFF to prevent rollover. */

        msr_value.low = 0xFFFF0000 | (hactive - 1);
        if (READ_REG32 (DC3_DISPLAY_CFG) & DC3_DCFG_DCEN)
        {
            msr_value.low += hblankstart - hactive;
            msr_value.low += htotal      - hblankend;
        }
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_REGAVAL, &msr_value);

        /* USE H4 FUNCTION A FOR DISPE AND H4 FUNCTION B FOR NOT DISPE */
        /* DISPE is bit 34                                             */

        msr_value.high = 0x00000002;
        msr_value.low  = 0x20000FF0;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_H0CTL + 4, &msr_value);

        /* USE H3 FUNCTION A FOR VSYNC AND H3 FUNCTION B FOR NOT VSYNC */
        /* VSYNC is bit 32.                                            */

        msr_value.high = 0x00000000;
        msr_value.low  = 0x002055AA;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_H0CTL + 3, &msr_value);
    }
    else if (crc_source == VG_CRC_SOURCE_PREFLICKER || crc_source == VG_CRC_SOURCE_PREFLICKER_EVEN)
    {
        diag = 0x801F8032;

        /* ENABLE HW CLOCK GATING AND SET GLCP CLOCK TO GEODELINK CLOCK */

	    msr_value.high = 0;
	    msr_value.low  = 5;
	    msr_write64 (MSR_DEVICE_GEODELX_GLCP, MSR_GEODELINK_PM, &msr_value);
	    msr_value.low = 0;
	    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DBGCLKCTL, &msr_value);
	    msr_value.low = 2;
	    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DBGCLKCTL, &msr_value);

        /* SET REGA LIMITS                              */
        /* Lower counter uses pixels/line               */
        /* Upper counter is 0xFFFF to prevent rollover. */

        msr_value.low = 0xFFFF0000 | (hactive - 1);
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_REGAVAL, &msr_value);

        /* USE H4 FUNCTION A FOR DISPE AND H4 FUNCTION B FOR NOT DISPE */
        /* DISPE is bit 47                                             */

        msr_value.high = 0x00000002;
        msr_value.low  = 0xF0000FF0;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_H0CTL + 4, &msr_value);

        /* USE H3 FUNCTION A FOR VSYNC AND H3 FUNCTION B FOR NOT VSYNC */
        /* VSYNC is bit 45.                                            */

        msr_value.high = 0x00000000;
        msr_value.low  = 0x002D55AA;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_H0CTL + 3, &msr_value);
    }
    else
    {
        /* PREFILTER CRC */

        diag = 0x80138048;
        msr_write64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_DIAG, &msr_value);

        /* ENABLE HW CLOCK GATING AND SET GLCP CLOCK TO GEODELINK CLOCK */

        msr_value.high = 0;
	    msr_value.low  = 5;
	    msr_write64 (MSR_DEVICE_GEODELX_GLCP, MSR_GEODELINK_PM, &msr_value);
	    msr_value.low = 0;
	    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DBGCLKCTL, &msr_value);
	    msr_value.low = 2;
	    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DBGCLKCTL, &msr_value);

        /* SET REGA LIMITS                                      */
        /* Lower counter uses pixels/line                       */
        /* Upper counter is 0xFFFF to prevent rollover.         */
        /* Note that we are assuming that the number of         */
        /* source pixels is specified in the FB_ACTIVE register */

        msr_value.low = 0xFFFF0000 | ((READ_REG32 (DC3_FB_ACTIVE) >> 16) & 0xFFF);
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_REGAVAL, &msr_value);

        /* USE H4 FUNCTION A FOR DISPE AND H4 FUNCTION B FOR NOT DISPE */
        /* DISPE is bit 55                                             */

        msr_value.high = 0x00000003;
        msr_value.low  = 0x70000FF0;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_H0CTL + 4, &msr_value);

        /* USE H3 FUNCTION A FOR VSYNC AND H3 FUNCTION B FOR NOT VSYNC */
        /* VSYNC is bit 53.                                            */

        msr_value.high = 0x00000000;
        msr_value.low  = 0x003555AA;
        msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_H0CTL + 3, &msr_value);
    }

    /* WAIT FOR THE CORRECT FIELD */
    /* We use the VG line count and field indicator to determine when */
    /* to kick off a CRC.                                             */

    if (crc_source & VG_CRC_SOURCE_EVEN) field = 0;
    else                                 field = DC3_LNCNT_EVEN_FIELD;

    if (READ_REG32 (DC3_IRQ_FILT_CTL) & DC3_IRQFILT_INTL_EN)
    {
        /* WAIT FOR THE BEGINNING OF THE FIELD (LINE 1-5) */
        /* Note that we wait for the field to be odd when CRCing the even */
        /* field and vice versa.  This is because the CRC will not begin  */
        /* until the following field.                                     */

        do
        {
            line = READ_REG32 (DC3_LINE_CNT_STATUS);
        } while ((line & DC3_LNCNT_EVEN_FIELD) != field    ||
                ((line & DC3_LNCNT_V_LINE_CNT) >> 16) < 1 ||
                ((line & DC3_LNCNT_V_LINE_CNT) >> 16) > 5);
    }
    else
    {
        /* NON-INTERLACED - EVEN FIELD CRCS ARE INVALID */

        if (crc_source & VG_CRC_SOURCE_EVEN)
            return 0xFFFFFFFF;
    }

    /* UPDATE VG DIAG OUTPUT */

    msr_value.high = 0;
    msr_value.low  = diag;
    msr_write64 (MSR_DEVICE_GEODELX_VG, MSR_GEODELINK_DIAG, &msr_value);

    /* CONFIGURE DIAG CONTROL */
    /* Set RegA action1 to increment lower 16 bits and clear at limit. (5)      */
    /* Set RegA action2 to increment upper 16 bits. (6)                         */
    /* Set RegB action1 to CRC32 (1)                                            */
    /* Set all comparators to REGA override (0,1 lower mbus, 2,3 upper mbus)    */
    /* Enable all actions                                                       */

    msr_value.low = 0x80EA20A0;
    msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DIAGCTL, &msr_value);
	
	/* DELAY TWO FRAMES */

    while (READ_REG32 (DC3_LINE_CNT_STATUS) & DC3_LNCNT_VNA);
	while (!(READ_REG32 (DC3_LINE_CNT_STATUS) & DC3_LNCNT_VNA));
	while (READ_REG32 (DC3_LINE_CNT_STATUS) & DC3_LNCNT_VNA);
	while (!(READ_REG32 (DC3_LINE_CNT_STATUS) & DC3_LNCNT_VNA));
	while (READ_REG32 (DC3_LINE_CNT_STATUS) & DC3_LNCNT_VNA);

	/* VERIFY THAT XSTATE = 11 */

	msr_read64 (MSR_DEVICE_GEODELX_GLCP, GLCP_XSTATE, &msr_value);
	if ((msr_value.low & 3) == 3)
	{
		msr_read64 (MSR_DEVICE_GEODELX_GLCP, GLCP_REGB, &msr_value);
		
		crc = msr_value.low;
	}

	/* DISABLE VG DIAG BUS OUTPUTS */

	msr_value.low  = 0x00000000;
	msr_value.high = 0x00000000;
	msr_write64 (MSR_DEVICE_GEODELX_VG,  MSR_GEODELINK_DIAG, &msr_value);

	/* DISABLE GLCP ACTIONS */

	msr_write64 (MSR_DEVICE_GEODELX_GLCP, GLCP_DIAGCTL, &msr_value);

    return crc;
}

/*---------------------------------------------------------------------------
 * vg_get_scaler_filter_coefficients
 *
 * This routine gets the vertical and horizontal filter coefficients for
 * graphics scaling.  The coefficients are sign extended to 32-bit values.
 *---------------------------------------------------------------------------*/

int vg_get_scaler_filter_coefficients (long h_taps[][5], long v_taps[][3])
{
	unsigned long irqfilt, i;
	unsigned long temp;
    long coeff0, coeff1, coeff2;
    unsigned long lock;
    	
	/* ENABLE ACCESS TO THE HORIZONTAL COEFFICIENTS */

    lock     = READ_REG32 (DC3_UNLOCK);
	irqfilt  = READ_REG32 (DC3_IRQ_FILT_CTL);
	irqfilt |= DC3_IRQFILT_H_FILT_SEL;
	
	/* WRITE COEFFICIENTS */
	/* Coefficient indexes do not auto-increment, so we must */
	/* write the address for every phase                     */

    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);

	for (i = 0; i < 256; i++)
	{
		WRITE_REG32 (DC3_IRQ_FILT_CTL, ((irqfilt & 0xFFFFFF00L) | i));

        temp = READ_REG32 (DC3_FILT_COEFF1);
        coeff0 = (temp & 0x3FF);
        coeff1 = (temp >> 10) & 0x3FF;
        coeff2 = (temp >> 20) & 0x3FF;

        h_taps[i][0] = (coeff0 << 22) >> 22;
        h_taps[i][1] = (coeff1 << 22) >> 22;
        h_taps[i][2] = (coeff2 << 22) >> 22;

        temp = READ_REG32 (DC3_FILT_COEFF2);
        coeff0 = (temp & 0x3FF);
        coeff1 = (temp >> 10) & 0x3FF;

        h_taps[i][3] = (coeff0 << 22) >> 22;
        h_taps[i][4] = (coeff1 << 22) >> 22;
	}

	/* ENABLE ACCESS TO THE VERTICAL COEFFICIENTS */

	irqfilt &= ~DC3_IRQFILT_H_FILT_SEL;

	/* WRITE COEFFICIENTS */

	for (i = 0; i < 256; i++)
	{
		WRITE_REG32 (DC3_IRQ_FILT_CTL, ((irqfilt & 0xFFFFFF00L) | i));

        temp = READ_REG32 (DC3_FILT_COEFF1);
        coeff0 = (temp & 0x3FF);
        coeff1 = (temp >> 10) & 0x3FF;
        coeff2 = (temp >> 20) & 0x3FF;

        v_taps[i][0] = (coeff0 << 22) >> 22;
        v_taps[i][1] = (coeff1 << 22) >> 22;
        v_taps[i][2] = (coeff2 << 22) >> 22;
	}

    WRITE_REG32 (DC3_UNLOCK, lock);

	return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_get_flicker_filter_configuration
 *
 * This routine returns the current VG flicker filter configuration.
 *---------------------------------------------------------------------------*/

int vg_get_flicker_filter_configuration (unsigned long *strength, int *flicker_alpha)
{
    unsigned long genlk_ctl;

    if (!strength || !flicker_alpha)
        return CIM_STATUS_INVALIDPARAMS;

    genlk_ctl = READ_REG32 (DC3_GENLK_CTL);
    *strength = genlk_ctl & DC3_GC_FLICKER_FILTER_MASK;
    if (genlk_ctl & DC3_GC_ALPHA_FLICK_ENABLE)
        *flicker_alpha = 1;
    else
        *flicker_alpha = 0;

    return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vg_get_display_pitch
 *
 * This routine returns the current stride between successive lines of frame
 * buffer data.
 *---------------------------------------------------------------------------*/

unsigned long vg_get_display_pitch (void)
{
	return ((READ_REG32 (DC3_GFX_PITCH) & 0x0000FFFF) << 3);
}

/*---------------------------------------------------------------------------
 * vg_get_frame_buffer_line_size
 *
 * This routine returns the current size in bytes of one line of frame buffer
 * data.
 *---------------------------------------------------------------------------*/

unsigned long vg_get_frame_buffer_line_size (void)
{
	return ((READ_REG32 (DC3_LINE_SIZE) & 0x3FF) << 3);
}

/*---------------------------------------------------------------------------
 * vg_get_current_vline
 *
 * This routine returns the number of the current line that is being displayed
 * by the display controller.
 *---------------------------------------------------------------------------*/

unsigned long vg_get_current_vline (void)
{
	unsigned long current_line;
	
	/* READ THE REGISTER TWICE TO ENSURE THAT THE VALUE IS NOT TRANSITIONING */
	
	do
	{
		current_line  = READ_REG32(DC3_LINE_CNT_STATUS) & DC3_LNCNT_V_LINE_CNT;
	}
	while (current_line != (READ_REG32(DC3_LINE_CNT_STATUS) & DC3_LNCNT_V_LINE_CNT));
		
	return (current_line >> 16);
}

/*---------------------------------------------------------------------------
 * vg_get_display_offset
 *
 * This routine returns the offset into the frame buffer for the first pixel
 * of the display.
 *---------------------------------------------------------------------------*/

unsigned long vg_get_display_offset (void)
{
	return (READ_REG32(DC3_FB_ST_OFFSET) & 0x0FFFFFFF);
}

/*---------------------------------------------------------------------------
 * vg_get_cursor_info
 *
 * This routine returns the current settings for the hardware cursor.
 *---------------------------------------------------------------------------*/

int vg_get_cursor_info (VG_CURSOR_DATA *cursor_data)
{
	unsigned long temp;

	/* CURSOR OFFSET */

	cursor_data->cursor_offset = READ_REG32 (DC3_CURS_ST_OFFSET) & 0x0FFFFFFF;
	
	/* CURSOR X POSITION */

	temp = READ_REG32 (DC3_CURSOR_X);
	cursor_data->cursor_x = temp & 0x7FF;
	cursor_data->clipx = (temp >> 11) & 0x3F;

	/* CURSOR Y POSITION */

	temp = READ_REG32 (DC3_CURSOR_Y);
	cursor_data->cursor_y = temp & 0x7FF;
	cursor_data->clipy = (temp >> 11) & 0x3F;

	/* CURSOR COLORS */

	WRITE_REG32 (DC3_PAL_ADDRESS, 0x100);
	cursor_data->mono_color0 = READ_REG32 (DC3_PAL_DATA);
	cursor_data->mono_color1 = READ_REG32 (DC3_PAL_DATA);

	/* CURSOR ENABLES */

	temp = READ_REG32 (DC3_GENERAL_CFG);
	if (temp & DC3_GCFG_CURE)    cursor_data->enable = 1;
	else                         cursor_data->enable = 0;
	if (temp & DC3_GCFG_CLR_CUR) cursor_data->color_cursor = 1;
	else                         cursor_data->color_cursor = 0;
	
	return CIM_STATUS_OK;
}

/*-----------------------------------------------------------------------------
 * vg_get_display_palette_entry
 *
 * This routine reads a single entry in the 8BPP display palette.
 *----------------------------------------------------------------------------*/

int vg_get_display_palette_entry (unsigned long index, unsigned long *entry)
{
	if (index > 0xFF)
		return CIM_STATUS_INVALIDPARAMS;

	WRITE_REG32 (DC3_PAL_ADDRESS, index);
	*entry = READ_REG32 (DC3_PAL_DATA);
	
	return CIM_STATUS_OK;
}

/*-----------------------------------------------------------------------------
 * vg_get_border_color
 *
 * This routine reads the current border color for centered displays.
 *----------------------------------------------------------------------------*/

unsigned long vg_get_border_color (void)
{
	WRITE_REG32 (DC3_PAL_ADDRESS,  0x104);
	return READ_REG32 (DC3_PAL_DATA);
}

/*-----------------------------------------------------------------------------
 * vg_get_display_palette
 *
 * This routines reads the entire contents of the display palette into a buffer.
 * The display palette consists of 256 X:R:G:B values.
 *----------------------------------------------------------------------------*/

int vg_get_display_palette (unsigned long *palette)
{
	unsigned long i;

	if (palette)
	{
		WRITE_REG32 (DC3_PAL_ADDRESS, 0);
		for (i = 0; i < 256; i++)
		{
			palette[i] = READ_REG32 (DC3_PAL_DATA);
		}
        return CIM_STATUS_OK;
	}
	return CIM_STATUS_INVALIDPARAMS;
}

/*-----------------------------------------------------------------------------
 * vg_get_compression_info
 *
 * This routines reads the current status of the display compression hardware.
 *----------------------------------------------------------------------------*/

int vg_get_compression_info (VG_COMPRESSION_DATA *comp_data)
{
	comp_data->compression_offset = READ_REG32 (DC3_CB_ST_OFFSET) & 0x0FFFFFFF;
	comp_data->pitch =  (READ_REG32 (DC3_GFX_PITCH) >> 13) & 0x7FFF8;
	comp_data->size  = ((READ_REG32 (DC3_LINE_SIZE) >> (DC3_LINE_SIZE_CB_SHIFT - 3)) & 0x3F8) + 24;

	return CIM_STATUS_OK;
}

/*-----------------------------------------------------------------------------
 * vg_get_compression_enable
 *
 * This routines reads the current enable status of the display compression hardware.
 *----------------------------------------------------------------------------*/

int vg_get_compression_enable (void)
{
	if (READ_REG32 (DC3_GENERAL_CFG) & DC3_GCFG_CMPE)
		return 1;
	
	return 0;
}

/*-----------------------------------------------------------------------------
 * vg_get_valid_bit
 *----------------------------------------------------------------------------*/

int vg_get_valid_bit (int line)
{
	unsigned long offset;
	unsigned long valid;
    unsigned long lock;
	
    lock    = READ_REG32 (DC3_UNLOCK);
	offset  = READ_REG32 (DC3_PHY_MEM_OFFSET) & 0xFF000000;
	offset |= line;

    WRITE_REG32 (DC3_UNLOCK, DC3_UNLOCK_VALUE);
	WRITE_REG32 (DC3_PHY_MEM_OFFSET, offset);
    WRITE_REG32 (DC3_UNLOCK, lock);
	valid = READ_REG32 (DC3_DV_ACC) & 2;
	
	if (valid) return 1;
	return 0;
}

#endif

