/*
 * Copyright (c) 2006 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Neither the name of the Advanced Micro Devices, Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 */

 /*
  * Cimarron display controller routines.  These routines program the display
  * mode and configure the hardware cursor and video buffers.
  */

/*---------------------*/
/* 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;

    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