/*
 * 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 VOP configuration routines.
  */

/*---------------------------------------------------------------------------
 * vop_set_vbi_window
 *
 * This routine configures the output position and location in memory of
 * VBI data.
 *--------------------------------------------------------------------------*/

int
vop_set_vbi_window(VOPVBIWINDOWBUFFER * buffer)
{
    unsigned long unlock, temp;
    unsigned long hstart, hstop;
    unsigned long htotal, hsyncstart;

    if (!buffer)
        return CIM_STATUS_INVALIDPARAMS;

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

    /* PROGRAM HORIZONTAL POSITION
     * The horizontal position is a little tricky.  The counter for the
     * horizontal timings is reused for the VBI counter.  Consequently, the
     * horizontal start and stop values are based off the beginning of active
     * data.  However, the VG has a quirk.  If the counter start position is
     * before the beginning of HSync, it applies to the previous line.  If
     * the counter is after the beginning of HSync it applies to the current
     * line.  So, for one line the real range can be thought of as
     * HSync_start to (HSync_start + htotal - 1).  However, the counters
     * must be between 0 and htotal - 1.  When placing VBI data before the
     * start of active data, the horizontal end position will thus be *less*
     * than the horizontal start.
     */

    htotal = ((READ_REG32(DC3_H_ACTIVE_TIMING) >> 16) & 0xFFF) + 1;
    hsyncstart = (READ_REG32(DC3_H_SYNC_TIMING) & 0xFFF) + 1;

    if (buffer->horz_from_hsync) {
        /* VERIFY THAT THE INPUT IS VALID */

        if (buffer->horz_start < 0
            || (buffer->horz_start + buffer->vbi_width) > htotal)
            return CIM_STATUS_INVALIDPARAMS;

        hstart = buffer->horz_start + hsyncstart;
    }
    else {
        /* VERIFY THAT THE INPUT IS VALID */

        if (buffer->horz_start < ((long) hsyncstart - (long) htotal) ||
            buffer->horz_start > (long) hsyncstart ||
            buffer->vbi_width > htotal) {
            return CIM_STATUS_INVALIDPARAMS;
        }

        hstart = buffer->horz_start + htotal;
    }

    hstop = hstart + buffer->vbi_width;
    if (hstart > htotal)
        hstart -= htotal;
    if (hstop > htotal)
        hstop -= htotal;
    hstart--;
    hstop--;
    WRITE_REG32(DC3_VBI_HOR, ((hstop << DC3_VBI_HOR_END_SHIFT) &
                              DC3_VBI_HOR_END_MASK) | (hstart &
                                                       DC3_VBI_HOR_START_MASK));

    /* WRITE LINE CAPTURE MASKS */

    WRITE_REG32(DC3_VBI_LN_ODD, ((buffer->odd_line_offset <<
                                  DC3_VBI_ODD_LINE_SHIFT) &
                                 DC3_VBI_ODD_LINE_MASK) |
                (buffer->odd_line_capture_mask & DC3_VBI_ODD_ENABLE_MASK));

    WRITE_REG32(DC3_VBI_LN_EVEN, ((buffer->even_line_offset <<
                                   DC3_VBI_EVEN_LINE_SHIFT) &
                                  DC3_VBI_EVEN_LINE_MASK) |
                (buffer->even_line_capture_mask & DC3_VBI_EVEN_ENABLE_MASK));

    /* PROGRAM SOURCE OFFSETS
     * Start with the even offsets.  Note that we always enable 16-bit VBI,
     * as this is the only way to get VBI data on each VOP clock.
     */

    temp = READ_REG32(DC3_VBI_EVEN_CTL) & ~DC3_VBI_EVEN_CTL_OFFSET_MASK;
    temp |= DC3_VBI_EVEN_CTL_ENABLE_16;
    if (buffer->enable_upscale)
        temp |= DC3_VBI_EVEN_CTL_UPSCALE;
    WRITE_REG32(DC3_VBI_EVEN_CTL, temp |
                (buffer->even_address_offset & DC3_VBI_EVEN_CTL_OFFSET_MASK));

    /* ODD OFFSET */

    temp = READ_REG32(DC3_VBI_ODD_CTL) & ~DC3_VBI_ODD_CTL_OFFSET_MASK;
    WRITE_REG32(DC3_VBI_ODD_CTL, temp |
                (buffer->odd_address_offset & DC3_VBI_ODD_CTL_OFFSET_MASK));

    /* PITCH */

    temp = ((buffer->data_size >> 3) << 16) | ((buffer->data_pitch >> 3) &
                                               0x0000FFFF);
    WRITE_REG32(DC3_VBI_PITCH, temp);

    WRITE_REG32(DC3_UNLOCK, unlock);

    return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vop_enable_vbi_output
 *
 * This routine enables/disables VBI fetching inside the video generator.
 *--------------------------------------------------------------------------*/

int
vop_enable_vbi_output(int enable)
{
    unsigned long unlock, temp;

    unlock = READ_REG32(DC3_UNLOCK);
    temp = READ_REG32(DC3_VBI_EVEN_CTL);

    if (enable)
        temp |= DC3_VBI_ENABLE;
    else
        temp &= ~DC3_VBI_ENABLE;

    WRITE_REG32(DC3_UNLOCK, DC3_UNLOCK_VALUE);
    WRITE_REG32(DC3_VBI_EVEN_CTL, temp);
    WRITE_REG32(DC3_UNLOCK, unlock);

    return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vop_set_configuration
 *
 * This routine is passed a VOP_CONFIGURATION structure that contains all
 * the necessary information to configure VOP output.
 *--------------------------------------------------------------------------*/

int
vop_set_configuration(VOPCONFIGURATIONBUFFER * config)
{
    unsigned long vop_config = 0;
    unsigned long alpha, control2;
    unsigned long unlock;
    unsigned long delta;
    Q_WORD msr_value;
    int rgb = 0;

    if (!config)
        return CIM_STATUS_INVALIDPARAMS;

    unlock = READ_REG32(DC3_UNLOCK);
    delta = READ_REG32(DC3_VID_DS_DELTA) & DC3_DS_DELTA_MASK;

    /* OVERRIDE THE OUTPUT SETTINGS TO ENABLE VOP OUTPUT */

    if (config->mode != VOP_MODE_DISABLED) {
        msr_read64(MSR_DEVICE_GEODELX_DF, MSR_GEODELINK_CONFIG, &msr_value);
        msr_value.low &= ~DF_CONFIG_OUTPUT_MASK;
        msr_value.low |= DF_OUTPUT_VOP;
        msr_write64(MSR_DEVICE_GEODELX_DF, MSR_GEODELINK_CONFIG, &msr_value);
    }

    /* SET THE UNIVERSAL VOP OPTIONS */

    if (config->flags & VOP_FLAG_SWAP_UV)
        vop_config |= VOP_CONFIG_SWAPUV;
    if (config->flags & VOP_FLAG_SWAP_VBI)
        vop_config |= VOP_CONFIG_SWAPVBI;

    /* SET THE MODE SPECIFIC PARAMETERS */

    if (config->mode == VOP_MODE_601) {
        vop_config |= config->vop601.flags;
        vop_config |= config->vop601.vsync_shift;
        vop_config |= VOP_CONFIG_ENABLE_601 | VOP_CONFIG_VIP2_0;

        switch (config->vop601.output_mode) {
        case VOP_601_YUV_16BIT:
            vop_config |= VOP_CONFIG_VIP2_16BIT;
            break;
        case VOP_601_YUV_4_4_4:
            vop_config |= VOP_CONFIG_DISABLE_DECIMATE;
            break;
        case VOP_601_RGB_8_8_8:
            vop_config |= VOP_CONFIG_DISABLE_DECIMATE | VOP_CONFIG_RGBMODE;
            rgb = 1;
            break;
        }

        if (config->vop601.vsync_shift == VOP_VSYNC_LATER_BY_X) {
            delta |= (config->vop601.vsync_shift_count &
                      DC3_601_VSYNC_SHIFT_MASK);
            delta |= DC3_601_VSYNC_SHIFT_ENABLE;
        }
    }
    else {
        if (config->flags & VOP_FLAG_VBI)
            vop_config |= VOP_CONFIG_VBI;
        if (config->flags & VOP_FLAG_TASK)
            vop_config |= VOP_CONFIG_TASK;
        if (config->flags & VOP_FLAG_SINGLECHIPCOMPAT)
            vop_config |= VOP_CONFIG_SC_COMPATIBLE;
        if (config->flags & VOP_FLAG_EXTENDEDSAV)
            vop_config |= VOP_CONFIG_EXTENDED_SAV;

        switch (config->mode) {
        case VOP_MODE_DISABLED:
            vop_config |= VOP_CONFIG_DISABLED;
            break;
        case VOP_MODE_VIP11:
            vop_config |= VOP_CONFIG_VIP1_1;
            break;
        case VOP_MODE_CCIR656:
            vop_config |= VOP_CONFIG_CCIR656;
            break;
        case VOP_MODE_VIP20_8BIT:
            vop_config |= VOP_CONFIG_VIP2_0;
            break;
        case VOP_MODE_VIP20_16BIT:
            vop_config |= VOP_CONFIG_VIP2_0 | VOP_CONFIG_VIP2_16BIT;
            break;
        }
    }

    /* SET THE 4:4:4 TO 4:2:2 DECIMATION ALGORITHM */

    vop_config |= (config->conversion_mode);

    /* SET THE VSYNC OUT OPTIONS */

    control2 = READ_VIP32(VIP_CONTROL2) & ~VIP_CONTROL2_SYNC2PIN_MASK;
    control2 |= config->vsync_out;
    WRITE_VIP32(VIP_CONTROL2, control2);

    /* FORCE THE CORRECT VOP COLOR SPACE */
    /* The output of the mixer will be either RGB or YUV.  We must enable */
    /* or disable the VOP CSC based on the desired output format.         */

    alpha = READ_VID32(DF_VID_ALPHA_CONTROL);
    if (!(alpha & DF_CSC_GRAPHICS_RGB_TO_YUV)) {
        /* RGB OUTPUT FROM THE MIXER */

        if (!rgb)
            alpha |= DF_CSC_VOP_RGB_TO_YUV;
        else
            alpha &= ~DF_CSC_VOP_RGB_TO_YUV;
    }
    else {
        /* YUV OUTPUT FROM THE MIXER */
        /* As there is no YUV->RGB VOP conversion, we simply disable the */
        /* VOP CSC and trust that the user is competent.                 */

        alpha &= ~DF_CSC_VOP_RGB_TO_YUV;
    }

    /* AND WRITE THE CONFIGURATION */

    WRITE_VID32(DF_VID_ALPHA_CONTROL, alpha);
    WRITE_VOP32(VOP_CONFIGURATION, vop_config);
    WRITE_REG32(DC3_UNLOCK, DC3_UNLOCK_VALUE);
    WRITE_REG32(DC3_VID_DS_DELTA, delta);
    WRITE_REG32(DC3_UNLOCK, unlock);

    return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vop_save_state
 *
 * This routine saves the necessary register contents in order to restore
 * at a later point to the same state.  Note that the capture state is
 * forced to OFF in this routine.
 *--------------------------------------------------------------------------*/

int
vop_save_state(VOPSTATEBUFFER * save_buffer)
{
    if (!save_buffer)
        return CIM_STATUS_INVALIDPARAMS;

    save_buffer->config = READ_VOP32(VOP_CONFIGURATION);

    return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vop_restore_state
 *
 * This routine restores the state of the vop registers - which were
 * previously saved using vop_save_state.
 *--------------------------------------------------------------------------*/

int
vop_restore_state(VOPSTATEBUFFER * restore_buffer)
{
    if (!restore_buffer)
        return CIM_STATUS_INVALIDPARAMS;

    WRITE_VOP32(VOP_CONFIGURATION, restore_buffer->config);

    return CIM_STATUS_OK;
}

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

#if CIMARRON_INCLUDE_VOP_READ_ROUTINES

/*---------------------------------------------------------------------------
 * vop_get_current_mode
 *
 * This routine reads the current VIP operating mode and stores it in the
 * passed VOP_CONFIGURATION structure.
 *--------------------------------------------------------------------------*/

int
vop_get_current_mode(VOPCONFIGURATIONBUFFER * config)
{
    unsigned long vop_config = 0;
    unsigned long alpha;

    if (!config)
        return CIM_STATUS_INVALIDPARAMS;

    vop_config = READ_VOP32(VOP_CONFIGURATION);
    alpha = READ_VID32(DF_VID_ALPHA_CONTROL);

    /* READ THE CURRENT MODE */

    switch (vop_config & VOP_CONFIG_MODE_MASK) {
    case VOP_CONFIG_DISABLED:
        config->mode = VOP_MODE_DISABLED;
        break;
    case VOP_CONFIG_VIP1_1:
        config->mode = VOP_MODE_VIP11;
        break;
    case VOP_CONFIG_CCIR656:
        config->mode = VOP_MODE_CCIR656;
        break;
    case VOP_CONFIG_VIP2_0:

        if (vop_config & VOP_CONFIG_ENABLE_601)
            config->mode = VOP_MODE_601;
        else if (vop_config & VOP_CONFIG_VIP2_16BIT)
            config->mode = VOP_MODE_VIP20_16BIT;
        else
            config->mode = VOP_MODE_VIP20_8BIT;
        break;
    }

    /* READ 601 SETTINGS */

    config->vop601.flags = vop_config & (VOP_CONFIG_INVERT_DISPE |
                                         VOP_CONFIG_INVERT_HSYNC |
                                         VOP_CONFIG_INVERT_VSYNC);

    config->vop601.vsync_shift = vop_config & VOP_CONFIG_VSYNC_MASK;
    config->vop601.vsync_shift_count =
        READ_REG32(DC3_VID_DS_DELTA) & DC3_601_VSYNC_SHIFT_MASK;

    if ((alpha & DF_CSC_GRAPHICS_RGB_TO_YUV) || (alpha & DF_CSC_VOP_RGB_TO_YUV)) {
        /* YUV OUTPUT */

        if (vop_config & VOP_CONFIG_DISABLE_DECIMATE)
            config->vop601.output_mode = VOP_601_YUV_4_4_4;
        else if (vop_config & VOP_CONFIG_VIP2_16BIT)
            config->vop601.output_mode = VOP_601_YUV_16BIT;
        else
            config->vop601.output_mode = VOP_601_YUV_8BIT;
    }
    else {
        config->vop601.output_mode = VOP_601_RGB_8_8_8;
    }

    config->flags = 0;

    /* READ THE UNIVERSAL VOP OPTIONS */

    if (vop_config & VOP_CONFIG_SWAPUV)
        config->flags |= VOP_FLAG_SWAP_UV;
    if (vop_config & VOP_CONFIG_SWAPVBI)
        config->flags |= VOP_FLAG_SWAP_VBI;
    if (vop_config & VOP_CONFIG_VBI)
        config->flags |= VOP_FLAG_VBI;
    if (vop_config & VOP_CONFIG_TASK)
        config->flags |= VOP_FLAG_TASK;
    if (vop_config & VOP_CONFIG_SC_COMPATIBLE)
        config->flags |= VOP_FLAG_SINGLECHIPCOMPAT;
    if (vop_config & VOP_CONFIG_EXTENDED_SAV)
        config->flags |= VOP_FLAG_EXTENDEDSAV;

    config->conversion_mode = vop_config & VOP_CONFIG_422_MASK;

    config->vsync_out = READ_VIP32(VIP_CONTROL2) & VIP_CONTROL2_SYNC2PIN_MASK;

    return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vop_get_vbi_window
 *
 * This routine reads the current VBI configuration for VOP output.
 *--------------------------------------------------------------------------*/

int
vop_get_vbi_configuration(VOPVBIWINDOWBUFFER * buffer)
{
    unsigned long temp;
    unsigned long hstart, hstop;
    unsigned long htotal, hsyncstart;

    if (!buffer)
        return CIM_STATUS_INVALIDPARAMS;

    htotal = ((READ_REG32(DC3_H_ACTIVE_TIMING) >> 16) & 0xFFF) + 1;
    hsyncstart = (READ_REG32(DC3_H_SYNC_TIMING) & 0xFFF) + 1;

    /* DECODE HORIZONTAL POSITION */
    /* This is done according to the requested horizontal origin */

    temp = READ_REG32(DC3_VBI_HOR);
    hstart = (temp & DC3_VBI_HOR_START_MASK) + 1;
    hstop = ((temp & DC3_VBI_HOR_END_MASK) >> DC3_VBI_HOR_END_SHIFT) + 1;
    if (buffer->horz_from_hsync) {
        buffer->horz_start = hstart + htotal - hsyncstart;
        if (buffer->horz_start >= (long) htotal)
            buffer->horz_start -= htotal;
    }
    else {
        if (hstart > hsyncstart)
            buffer->horz_start = (long) hstart - (long) htotal;
        else
            buffer->horz_start = hstart;
    }

    if (hstop > hstart)
        buffer->vbi_width = hstop - hstart;
    else
        buffer->vbi_width = (htotal - hstart) + hstop;

    /* READ LINE MASKS */

    temp = READ_REG32(DC3_VBI_LN_ODD);
    buffer->odd_line_offset = (temp & DC3_VBI_ODD_LINE_MASK) >>
        DC3_VBI_ODD_LINE_SHIFT;
    buffer->odd_line_capture_mask = (temp & DC3_VBI_ODD_ENABLE_MASK);

    temp = READ_REG32(DC3_VBI_LN_EVEN);
    buffer->even_line_offset = (temp & DC3_VBI_EVEN_LINE_MASK) >>
        DC3_VBI_EVEN_LINE_SHIFT;
    buffer->even_line_capture_mask = (temp & DC3_VBI_EVEN_ENABLE_MASK);

    /* READ VBI UPSCALE SETTINGS */

    buffer->enable_upscale = 0;
    temp = READ_REG32(DC3_VBI_EVEN_CTL);
    if (temp & DC3_VBI_EVEN_CTL_UPSCALE)
        buffer->enable_upscale = 1;

    /* READ SOURCE OFFSETS */

    buffer->even_address_offset = temp & DC3_VBI_EVEN_CTL_OFFSET_MASK;
    buffer->odd_address_offset =
        READ_REG32(DC3_VBI_ODD_CTL) & DC3_VBI_ODD_CTL_OFFSET_MASK;

    /* PITCH AND SIZE */

    temp = READ_REG32(DC3_VBI_PITCH);
    buffer->data_size = (temp >> 16) << 3;
    buffer->data_pitch = (temp & 0xFFFF);

    return CIM_STATUS_OK;
}

/*---------------------------------------------------------------------------
 * vop_get_vbi_enable
 *
 * This routine reads the current enable status of VBI output.
 *--------------------------------------------------------------------------*/

int
vop_get_vbi_enable(void)
{
    if (READ_REG32(DC3_VBI_EVEN_CTL) & DC3_VBI_ENABLE)
        return 1;

    return 0;
}

/*---------------------------------------------------------------------------
 * vop_get_crc
 *
 * This routine returns a CRC of the current VOP data
 --------------------------------------------------------------------------*/

unsigned long
vop_get_crc(void)
{
    unsigned long crc;
    unsigned long config = READ_VOP32(VOP_CONFIGURATION);
    unsigned long timeout = 1000;

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

    /* RESET CRC */

    WRITE_VOP32(VOP_CONFIGURATION, config & ~VOP_CONFIG_ENABLE_SIGNATURE);

    /* WAIT FOR THE RESET TO BE LATCHED */

    while ((READ_VOP32(VOP_SIGNATURE) != 0x00000001) && timeout)
        timeout--;

    WRITE_VOP32(VOP_CONFIGURATION, config | VOP_CONFIG_ENABLE_SIGNATURE);

    /* WAIT UNTIL NOT ACTIVE, THEN ACTIVE, NOT ACTIVE, THEN ACTIVE */

    while (!(READ_VOP32(VOP_CONFIGURATION) & VOP_CONFIG_SIGVAL));

    crc = READ_VOP32(VOP_SIGNATURE);

    return crc;
}

/*---------------------------------------------------------------------------
 * vop_read_vbi_crc
 *
 * This routine returns a CRC of the current VBI data
 ---------------------------------------------------------------------------*/

unsigned long
vop_read_vbi_crc(void)
{
    unsigned long gcfg, unlock, vbi_even;
    unsigned long crc;

    if (!(READ_REG32(DC3_DISPLAY_CFG) & DC3_DCFG_TGEN) ||
        !(READ_REG32(DC3_VBI_EVEN_CTL) & DC3_VBI_ENABLE)) {
        return 0xFFFFFFFF;
    }

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

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

    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;
}

#endif
