 /*
  * <LIC_AMD_STD>
  * Copyright (C) 2005 Advanced Micro Devices, Inc.  All Rights Reserved.
  * </LIC_AMD_STD>
  *
  * <CTL_AMD_STD>
  * </CTL_AMD_STD>
  *
  * <DOC_AMD_STD>
  * Cimarron VOP configuration routines.
  * </DOC_AMD_STD>
  *
  */

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

