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

        Part of Radeon accelerant
                
        Display Power Management (DPMS) support
*/

#include "radeon_accelerant.h"
#include "mmio.h"
#include "crtc_regs.h"
#include "fp_regs.h"
#include "pll_regs.h"
#include "pll_access.h"
#include "tv_out_regs.h"
#include "theatre_regs.h"
#include "GlobalData.h"
#include "generic.h"


// public function: set DPMS mode
status_t SET_DPMS_MODE(uint32 dpms_flags) 
{
        virtual_card *vc = ai->vc;
        status_t 
                result1 = B_OK, 
                result2 = B_OK;
        
        if( vc->used_crtc[0] )
                result1 = Radeon_SetDPMS( ai, 0, dpms_flags );
        if( vc->used_crtc[0] )
                result1 = Radeon_SetDPMS( ai, 0, dpms_flags );

        if( result1 == B_OK && result2 == B_OK )
                return B_OK;
        else
                return B_ERROR;
}

// public function: report DPMS capabilities
uint32 DPMS_CAPABILITIES(void) 
{
        return  B_DPMS_ON | B_DPMS_STAND_BY  | B_DPMS_SUSPEND | B_DPMS_OFF;
}


// public function: get current DPMS mode
uint32 DPMS_MODE(void) 
{
        // we just ask the primary virtual head what status it is in
        return Radeon_GetDPMS( ai, ai->vc->used_crtc[0] ? 0 : 1 );
}


// set DPMS state of LVDS port
static void Radeon_SetDPMS_LVDS( accelerator_info *ai, int mode )
{
        vuint8 *regs = ai->regs;

        // for internal flat panel, switch backlight off too
        switch( mode ) {
        case B_DPMS_ON:
                // on my laptop, the display has problems to wake-up, this
                // should hopefully cure that
                // (you get a dark picture first that becomes brighter step by step,
                //  after a couple of seconds you have full brightness again)
                OUTREGP( regs, RADEON_LVDS_GEN_CNTL, RADEON_LVDS_BLON, ~RADEON_LVDS_BLON );
                snooze( ai->si->panel_pwr_delay * 1000 ); 
                OUTREGP( regs, RADEON_LVDS_GEN_CNTL, RADEON_LVDS_ON, ~RADEON_LVDS_ON );
                break;
                
        case B_DPMS_STAND_BY:
        case B_DPMS_SUSPEND:
        case B_DPMS_OFF: {
                uint32 old_pixclks_cntl;
                
                old_pixclks_cntl = Radeon_INPLL( ai->regs, ai->si->asic, RADEON_PIXCLKS_CNTL);
                
                // ASIC bug: when LVDS_ON is reset, LVDS_ALWAYS_ON must be zero
                if( ai->si->is_mobility || ai->si->is_igp ) 
                        Radeon_OUTPLLP( ai->regs, ai->si->asic, RADEON_PIXCLKS_CNTL, 0, ~RADEON_PIXCLK_LVDS_ALWAYS_ONb );

                OUTREGP( regs, RADEON_LVDS_GEN_CNTL, 0, ~(RADEON_LVDS_BLON | RADEON_LVDS_ON) );
                        
                if( ai->si->is_mobility || ai->si->is_igp ) 
                        Radeon_OUTPLL( ai->regs, ai->si->asic, RADEON_PIXCLKS_CNTL, old_pixclks_cntl );

                break; }
        }
}


// set DPMS state of DVI port
static void Radeon_SetDPMS_DVI( accelerator_info *ai, int mode )
{
        vuint8 *regs = ai->regs;

        // it seems that DPMS doesn't work on DVI, so we disable FP completely
        // (according to specs this is the official way to handle DVI though DPMS
        // *should* be supported as well)
        switch( mode ) {
        case B_DPMS_ON:
                OUTREGP( regs, RADEON_FP_GEN_CNTL, RADEON_FP_FPON | RADEON_FP_TMDS_EN, 
                        ~(RADEON_FP_FPON | RADEON_FP_TMDS_EN));
                break;
        case B_DPMS_STAND_BY:
        case B_DPMS_SUSPEND:
        case B_DPMS_OFF:
                OUTREGP( regs, RADEON_FP_GEN_CNTL, 0, ~RADEON_FP_FPON | RADEON_FP_TMDS_EN );
                break;
        }
}


// set DPMS state of external DVI port
static void Radeon_SetDPMS_FP2( accelerator_info *ai, int mode )
{
        vuint8 *regs = ai->regs;

        // it seems that DPMS doesn't work on DVI, so we disable FP completely
        // (according to specs this is the official way to handle DVI though DPMS
        // *should* be supported as well)
        switch( mode ) {
        case B_DPMS_ON:
                OUTREGP( regs, RADEON_FP2_GEN_CNTL, 0, ~RADEON_FP2_BLANK_EN);
                OUTREGP( regs, RADEON_FP2_GEN_CNTL, RADEON_FP2_FPON, ~RADEON_FP2_FPON);
                if (ai->si->asic >= rt_r200) {
                        OUTREGP( regs, RADEON_FP2_GEN_CNTL, RADEON_FP2_DV0_EN, ~RADEON_FP2_DV0_EN);
                }
                break;
        case B_DPMS_STAND_BY:
        case B_DPMS_SUSPEND:
        case B_DPMS_OFF:
                OUTREGP( regs, RADEON_FP2_GEN_CNTL, RADEON_FP2_BLANK_EN, ~RADEON_FP2_BLANK_EN );
                OUTREGP( regs, RADEON_FP2_GEN_CNTL, 0, ~RADEON_FP2_FPON);
                if (ai->si->asic >= rt_r200) {
                        OUTREGP( regs, RADEON_FP2_GEN_CNTL, 0, ~RADEON_FP2_DV0_EN);
                }
                break;
        }
}


// set DPMS mode for CRT DAC.
// warning: the CRTC-DAC only obbeys this setting if
// connected to CRTC1, else it collides with TV-DAC
static void Radeon_SetDPMS_CRT( accelerator_info *ai, int mode )
{
        vuint8 *regs = ai->regs;
        
        switch( mode ) {
        case B_DPMS_ON:
                OUTREGP( regs, RADEON_CRTC_EXT_CNTL, 0, ~RADEON_CRTC_DISPLAY_DIS );
                break;
                
        case B_DPMS_STAND_BY:
        case B_DPMS_SUSPEND:
        case B_DPMS_OFF:
                OUTREGP( regs, RADEON_CRTC_EXT_CNTL,
                        RADEON_CRTC_DISPLAY_DIS, ~RADEON_CRTC_DISPLAY_DIS );
                break;
        }
}


// set DPMS mode for TV-DAC in CRT mode
// warning: if the CRT-DAC is connected to CRTC2, it is
// affected by this setting too
static void Radeon_SetDPMS_TVCRT( accelerator_info *ai, int mode )
{
        vuint8 *regs = ai->regs;
        
        switch( mode ) {
        case B_DPMS_ON:
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, 0, 
                        ~(RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_VSYNC_DIS | RADEON_CRTC2_HSYNC_DIS) );
                break;
        case B_DPMS_STAND_BY:
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, (RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_HSYNC_DIS), 
                        ~(RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_VSYNC_DIS | RADEON_CRTC2_HSYNC_DIS) );
                break;
        case B_DPMS_SUSPEND:
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, (RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_VSYNC_DIS), 
                        ~(RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_VSYNC_DIS | RADEON_CRTC2_HSYNC_DIS) );
                break;
        case B_DPMS_OFF:
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, (RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_VSYNC_DIS | RADEON_CRTC2_HSYNC_DIS), 
                        ~(RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_VSYNC_DIS | RADEON_CRTC2_HSYNC_DIS) );
                break;
        }
}


// set DPMS mode for first CRTC
static void Radeon_SetDPMS_CRTC1( accelerator_info *ai, int mode )
{
        vuint8 *regs = ai->regs;
        
        uint32 mask = ~(RADEON_CRTC_DISPLAY_DIS | RADEON_CRTC_VSYNC_DIS | RADEON_CRTC_HSYNC_DIS);
                
        switch( mode ) {
        case B_DPMS_ON:
                OUTREGP( regs, RADEON_CRTC_EXT_CNTL, 0, mask );
                break;
        case B_DPMS_STAND_BY:
                OUTREGP( regs, RADEON_CRTC_EXT_CNTL, 
                        (RADEON_CRTC_DISPLAY_DIS | RADEON_CRTC_HSYNC_DIS), mask );
                break;
        case B_DPMS_SUSPEND:
                OUTREGP( regs, RADEON_CRTC_EXT_CNTL, 
                        (RADEON_CRTC_DISPLAY_DIS | RADEON_CRTC_VSYNC_DIS), mask );
                break;
        case B_DPMS_OFF:
                OUTREGP( regs, RADEON_CRTC_EXT_CNTL, 
                        (RADEON_CRTC_DISPLAY_DIS | RADEON_CRTC_VSYNC_DIS | RADEON_CRTC_HSYNC_DIS), mask );
                break;
        }
        
        // disable/enable memory requests and cursor
        switch( mode ) {
        case B_DPMS_ON:
                /* Screen: On; HSync: On, VSync: On */
                OUTREGP( regs, RADEON_CRTC_GEN_CNTL, 0, ~RADEON_CRTC_DISP_REQ_EN_B );
                Radeon_ShowCursor( ai, 0 );
                break;
        case B_DPMS_STAND_BY:
        case B_DPMS_SUSPEND:
        case B_DPMS_OFF:
                OUTREGP( regs, RADEON_CRTC_GEN_CNTL, RADEON_CRTC_DISP_REQ_EN_B, 
                        ~(RADEON_CRTC_DISP_REQ_EN_B | RADEON_CRTC_CUR_EN) );
                break;
        }
}


// set DPMS mode of second CRTC
static void Radeon_SetDPMS_CRTC2( accelerator_info *ai, int mode )
{
        vuint8 *regs = ai->regs;
        
        int mask = ~(RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_VSYNC_DIS | RADEON_CRTC2_HSYNC_DIS);
                
        switch( mode ) {
        case B_DPMS_ON:
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, 0, mask );
                break;
        case B_DPMS_STAND_BY:
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, (RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_HSYNC_DIS), mask );
                break;
        case B_DPMS_SUSPEND:
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, (RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_VSYNC_DIS), mask);
                break;
        case B_DPMS_OFF:
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, 
                        (RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_VSYNC_DIS | RADEON_CRTC2_HSYNC_DIS), mask);
                break;
        }
        
        switch( mode ) {
        case B_DPMS_ON:
                /* Screen: On; HSync: On, VSync: On */
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, 0, ~RADEON_CRTC2_DISP_REQ_EN_B );
                Radeon_ShowCursor( ai, 1 );
                break;
        case B_DPMS_STAND_BY:
        case B_DPMS_SUSPEND:
        case B_DPMS_OFF:
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, RADEON_CRTC2_DISP_REQ_EN_B, 
                        ~(RADEON_CRTC2_DISP_REQ_EN_B | RADEON_CRTC2_CUR_EN) );
                break;
        }
}


// set DPMS mode of TV-out
static void Radeon_SetDPMS_TVOUT( accelerator_info *ai, int mode )
{
        // we set to gain either to 0 for blank or 1 for normal operation
        if( IS_INTERNAL_TV_OUT( ai->si->tv_chip )) {
                OUTREG( ai->regs, RADEON_TV_LINEAR_GAIN_SETTINGS,
                        mode == B_DPMS_ON ? 0x01000100 : 0 );
        } else {
                Radeon_VIPWrite( ai, ai->si->theatre_channel, RADEON_TV_LINEAR_GAIN_SETTINGS, 
                        mode == B_DPMS_ON ? 0x01000100 : 0 );
        }
}

// set DPMS mode of one port
// engine lock is assumed to be hold
status_t Radeon_SetDPMS( accelerator_info *ai, int crtc_idx, int mode )
{
        crtc_info *crtc = &ai->si->crtc[crtc_idx];
        
        // test validity of mode once and for all       
        switch( mode ) {
        case B_DPMS_ON:
        case B_DPMS_STAND_BY:
        case B_DPMS_SUSPEND:
        case B_DPMS_OFF:
                break;
        default:
                return B_BAD_VALUE;
        }

        if( crtc_idx == 0 )
                Radeon_SetDPMS_CRTC1( ai, mode );
        else
                Radeon_SetDPMS_CRTC2( ai, mode );

        // possible ASIC bug: if CRT-DAC is connected to CRTC1, it obbeys 
        // RADEON_CRTC_DISPLAY_DIS; if it is connected to CRTC2, to
        // RADEON_CRTC2_DISP_DIS - i.e. it follows the CRTC;
        // but the TV-DAC always listens to RADEON_CRTC2_DISP_DIS, independant
        // of the CRTC it gets its signal from;
        // this is a guarantee that two virtual cards will collide!
        if( crtc_idx == 0 || 1/* && (crtc->active_displays & dd_crt) != 0 */)
                Radeon_SetDPMS_CRT( ai, mode );

        if( crtc_idx == 1 || (crtc->active_displays & (dd_tv_crt | dd_ctv | dd_stv)) != 0 )
                Radeon_SetDPMS_TVCRT( ai, mode );

        // TV-Out ignores DPMS completely, including the blank-screen trick             
        if( (crtc->active_displays & (dd_ctv | dd_stv)) != 0 )
                Radeon_SetDPMS_TVOUT( ai, mode );

        if( (crtc->active_displays & dd_lvds) != 0 )
                Radeon_SetDPMS_LVDS( ai, mode );

        if( (crtc->active_displays & dd_dvi) != 0 )
                Radeon_SetDPMS_DVI( ai, mode );
                
        if( (crtc->active_displays & dd_dvi_ext) != 0 )
                Radeon_SetDPMS_FP2( ai, mode );
                
        return B_OK;
}


// get DPMS mode of first port
static uint32 Radeon_GetDPMS_CRTC1( accelerator_info *di )
{
        uint32 tmp;
        
        tmp = INREG( di->regs, RADEON_CRTC_EXT_CNTL );
        
        if( (tmp & RADEON_CRTC_DISPLAY_DIS) == 0 )
                return B_DPMS_ON;
                
        if( (tmp & RADEON_CRTC_VSYNC_DIS) == 0 )
                return B_DPMS_STAND_BY;
                
        if( (tmp & RADEON_CRTC_HSYNC_DIS) == 0 )
                return B_DPMS_SUSPEND;
                
        return B_DPMS_OFF;
}


// get DPMS mode of second port
static uint32 Radeon_GetDPMS_CRTC2( accelerator_info *di )
{
        uint32 tmp;
        
        tmp = INREG( di->regs, RADEON_CRTC2_GEN_CNTL );
        
        if( (tmp & RADEON_CRTC2_DISP_DIS) == 0 )
                return B_DPMS_ON;
                
        if( (tmp & RADEON_CRTC2_VSYNC_DIS) == 0 )
                return B_DPMS_STAND_BY;
                
        if( (tmp & RADEON_CRTC2_HSYNC_DIS) == 0 )
                return B_DPMS_SUSPEND;
                
        return B_DPMS_OFF;
}


// get DPMS mode of one port
uint32 Radeon_GetDPMS( accelerator_info *ai, int crtc_idx )
{
        if( crtc_idx == 0 )
                return Radeon_GetDPMS_CRTC1( ai );
        else
                return Radeon_GetDPMS_CRTC2( ai );
}