root/src/add-ons/accelerants/radeon/crtc.c
/*
        Copyright (c) 2002-2005, Thomas Kurschel
        

        Part of Radeon accelerant
                
        CRTC programming
*/

#include "radeon_accelerant.h"
#include "mmio.h"
#include "crtc_regs.h"
#include "GlobalData.h"
#include "set_mode.h"
#include "generic.h"


// hammer CRTC registers
void Radeon_ProgramCRTCRegisters( accelerator_info *ai, int crtc_idx, 
        crtc_regs *values )
{
        vuint8 *regs = ai->regs;
        
        SHOW_FLOW0( 2, "" );

        if( crtc_idx == 0 ) {
                OUTREGP( regs, RADEON_CRTC_GEN_CNTL, values->crtc_gen_cntl,
                        RADEON_CRTC_EXT_DISP_EN );
                
                OUTREG( regs, RADEON_CRTC_H_TOTAL_DISP, values->crtc_h_total_disp );
                OUTREG( regs, RADEON_CRTC_H_SYNC_STRT_WID, values->crtc_h_sync_strt_wid );
                OUTREG( regs, RADEON_CRTC_V_TOTAL_DISP, values->crtc_v_total_disp );
                OUTREG( regs, RADEON_CRTC_V_SYNC_STRT_WID, values->crtc_v_sync_strt_wid );
                OUTREG( regs, RADEON_CRTC_OFFSET_CNTL, values->crtc_offset_cntl );
                OUTREG( regs, RADEON_CRTC_PITCH, values->crtc_pitch );

        } else {
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, values->crtc_gen_cntl,
                        RADEON_CRTC2_VSYNC_DIS |
                        RADEON_CRTC2_HSYNC_DIS |
                        RADEON_CRTC2_DISP_DIS |
                        RADEON_CRTC2_CRT2_ON );

                OUTREG( regs, RADEON_CRTC2_H_TOTAL_DISP, values->crtc_h_total_disp );
                OUTREG( regs, RADEON_CRTC2_H_SYNC_STRT_WID, values->crtc_h_sync_strt_wid );
                OUTREG( regs, RADEON_CRTC2_V_TOTAL_DISP, values->crtc_v_total_disp );
                OUTREG( regs, RADEON_CRTC2_V_SYNC_STRT_WID, values->crtc_v_sync_strt_wid );
                OUTREG( regs, RADEON_CRTC2_OFFSET_CNTL, values->crtc_offset_cntl );
                OUTREG( regs, RADEON_CRTC2_PITCH, values->crtc_pitch );
        }
}


// get required hsync delay depending on bit depth and output device
uint16 Radeon_GetHSyncFudge( crtc_info *crtc, int datatype )
{
        static int hsync_fudge_default[] = { 0x00, 0x12, 0x09, 0x09, 0x06, 0x05 };
        static int hsync_fudge_fp[]      = { 0x02, 0x02, 0x00, 0x00, 0x05, 0x05 };
        
        // there is an sync delay which depends on colour-depth and output device
        if( (crtc->chosen_displays & (dd_dvi | dd_dvi_ext | dd_lvds )) != 0 )
                return hsync_fudge_fp[datatype - 1];
    else               
        return hsync_fudge_default[datatype - 1];       
}


// calculate CRTC register content
void Radeon_CalcCRTCRegisters( accelerator_info *ai, crtc_info *crtc, 
        display_mode *mode, crtc_regs *values )
{
        virtual_card *vc = ai->vc;
    int    hsync_start;
    int    hsync_wid;
    int    hsync_fudge;
    int    vsync_wid;

        hsync_fudge = Radeon_GetHSyncFudge( crtc, vc->datatype );

        if( crtc->crtc_idx == 0 ) {
                // here, we should set interlace/double scan mode
                // but we don't support them (anyone missing them?)
            values->crtc_gen_cntl = 
                        RADEON_CRTC_EN
                        | (vc->datatype << 8);

        } else {
                values->crtc_gen_cntl = RADEON_CRTC2_EN
                        | (vc->datatype << 8)
                        | (0/*doublescan*/ ? RADEON_CRTC2_DBL_SCAN_EN   : 0)
                        | ((mode->timing.flags & B_TIMING_INTERLACED)
                                ? RADEON_CRTC2_INTERLACE_EN     : 0);
        }
        
    values->crtc_h_total_disp = 
        ((mode->timing.h_total / 8 - 1) & RADEON_CRTC_H_TOTAL)
                | (((mode->timing.h_display / 8 - 1) << RADEON_CRTC_H_DISP_SHIFT) & RADEON_CRTC_H_DISP);

    hsync_wid = (mode->timing.h_sync_end - mode->timing.h_sync_start) / 8;

    hsync_start = mode->timing.h_sync_start - 8 + hsync_fudge;

    values->crtc_h_sync_strt_wid = 
        (hsync_start & (RADEON_CRTC_H_SYNC_STRT_CHAR | RADEON_CRTC_H_SYNC_STRT_PIX))
                | (hsync_wid << RADEON_CRTC_H_SYNC_WID_SHIFT)
                | ((mode->flags & B_POSITIVE_HSYNC) == 0 ? RADEON_CRTC_H_SYNC_POL : 0);
  
        values->crtc_v_total_disp = 
        ((mode->timing.v_total - 1) & RADEON_CRTC_V_TOTAL)
                | (((mode->timing.v_display - 1) << RADEON_CRTC_V_DISP_SHIFT) & RADEON_CRTC_V_DISP);

    vsync_wid = mode->timing.v_sync_end - mode->timing.v_sync_start;

        values->crtc_v_sync_strt_wid = 
        ((mode->timing.v_sync_start - 1) & RADEON_CRTC_V_SYNC_STRT)
                | (vsync_wid << RADEON_CRTC_V_SYNC_WID_SHIFT)
                | ((mode->flags & B_POSITIVE_VSYNC) == 0
                    ? RADEON_CRTC_V_SYNC_POL : 0);

    values->crtc_offset_cntl = 0;

        values->crtc_pitch = Radeon_RoundVWidth( mode->virtual_width, vc->bpp ) / 8;

        SHOW_FLOW( 2, "crtc_pitch=%ld", values->crtc_pitch );
        
    values->crtc_pitch |= values->crtc_pitch << 16;
}


// update shown are of one port
static void moveOneDisplay( accelerator_info *ai, crtc_info *crtc )
{
        virtual_card *vc = ai->vc;
        uint32 offset;
        
        offset = (vc->mode.v_display_start + crtc->rel_y) * vc->pitch + 
                (vc->mode.h_display_start + crtc->rel_x) * vc->bpp + 
                vc->fb_offset;
        
        SHOW_FLOW( 3, "Setting address %x on port %d", 
                offset, crtc->crtc_idx );
        
        OUTREG( ai->regs, crtc->crtc_idx == 0 ? RADEON_CRTC_OFFSET : RADEON_CRTC2_OFFSET, offset );
}

// internal function: pan display
// engine lock should be hold
status_t Radeon_MoveDisplay( accelerator_info *ai, uint16 h_display_start, uint16 v_display_start )
{
        virtual_card *vc = ai->vc;
        
        SHOW_FLOW( 4, "h_display_start=%ld, v_display_start=%ld",
                h_display_start, v_display_start );

        if( h_display_start + vc->eff_width > vc->mode.virtual_width ||
                v_display_start + vc->eff_height > vc->mode.virtual_height )
                return B_ERROR;

        // this is needed both for get_mode_info and for scrolling of virtual screens
        vc->mode.h_display_start = h_display_start & ~7;
        vc->mode.v_display_start = v_display_start;

        // do it
        if( vc->used_crtc[0] )
                moveOneDisplay( ai, &ai->si->crtc[0] );
        if( vc->used_crtc[1] )
                moveOneDisplay( ai, &ai->si->crtc[1] );
                
        // overlay position must be adjusted 
        Radeon_UpdateOverlay( ai );
        
        return B_OK;
}

// public function: pan display
status_t MOVE_DISPLAY( uint16 h_display_start, uint16 v_display_start ) 
{
        shared_info *si = ai->si;
        status_t result;
                
        ACQUIRE_BEN( si->engine.lock );
        
        // TBD: we should probably lock card first; in this case, we must
        //      split this function into locking and worker part, as this
        //      function is used internally as well
        result = Radeon_MoveDisplay( ai, h_display_start, v_display_start );
                
        RELEASE_BEN( si->engine.lock );
                        
        return result;
}