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

        Part of Radeon accelerant
                
        Multi-monitor management
*/

#include "radeon_accelerant.h"
#include "generic.h"
#include "GlobalData.h"


// transform official mode to internal, multi-screen mode enhanced mode
void Radeon_DetectMultiMode( virtual_card *vc, display_mode *mode )
{
        (void)vc;
        
        mode->timing.flags &= ~RADEON_MODE_MASK;
        
        // combine mode is used if virtual area is twice as visible area 
        // and if scrolling is enabled; if combining is impossible, use
        // cloning instead
        if( (mode->flags & B_SCROLL) == 0 )
                return;
        
        SHOW_FLOW0( 3, "possibly combine mode" );

        // remove scroll flag - we don't need it anymore
        mode->flags &= ~B_SCROLL;
                
        mode->timing.flags &= ~RADEON_MODE_POSITION_MASK;

        if( mode->virtual_width == 2 * mode->timing.h_display ) {
                SHOW_FLOW0( 2, "horizontal combine mode" );
                mode->timing.flags |= RADEON_MODE_POSITION_HORIZONTAL;
                mode->timing.flags &= ~RADEON_MODE_MASK;
                mode->timing.flags |= RADEON_MODE_COMBINE;
        } else if( mode->virtual_height == 2 * mode->timing.v_display ) {
                SHOW_FLOW0( 2, "vertical combine mode" );
                mode->timing.flags |= RADEON_MODE_POSITION_VERTICAL;
                mode->timing.flags &= ~RADEON_MODE_MASK;
                mode->timing.flags |= RADEON_MODE_COMBINE;
        } else {
                // ups, this isn't really a combine mode - restore flags
                SHOW_FLOW0( 2, "wasn't really a combine mode" );
                mode->timing.flags &= ~RADEON_MODE_MASK;
                mode->flags |= B_SCROLL;
        }
}

// make sure selected multi-screen mode is valid; adapt it if needed
void Radeon_VerifyMultiMode( virtual_card *vc, shared_info *si, display_mode *mode )
{
        // if there is no second port or no second monitor connected,
        // fall back to standard mode
        int num_usable_crtcs = vc->assigned_crtc[0] && si->crtc[0].chosen_displays != dd_none;
        
        if( si->num_crtc > 1 )
                num_usable_crtcs += vc->assigned_crtc[1] && si->crtc[1].chosen_displays != dd_none;
                
        if( num_usable_crtcs < 2 ) {
                SHOW_FLOW0( 2, "only one monitor - disabling any multi-mon mode" );
                // restore flags if combine mode is selected
                if( (mode->timing.flags & RADEON_MODE_MASK) == RADEON_MODE_COMBINE )
                        mode->flags |= B_SCROLL;

                mode->timing.flags &= ~RADEON_MODE_MASK;
                mode->timing.flags |= RADEON_MODE_STANDARD;
        }
}

// transform internal, multi-screen enabled display mode
// to official mode
void Radeon_HideMultiMode( virtual_card *vc, display_mode *mode )
{
        (void) vc;
        
        // restore flags for combine mode
        if( (mode->timing.flags & RADEON_MODE_MASK) == RADEON_MODE_COMBINE )
                mode->flags |= B_SCROLL;
}


// initialize multi-screen mode dependant variables
void Radeon_InitMultiModeVars( 
        accelerator_info *ai, display_mode *mode )
{
        virtual_card *vc = ai->vc;
        shared_info *si = ai->si;
        uint32 x, y;

        // setup single-screen mode
        vc->eff_width = mode->timing.h_display;
        vc->eff_height = mode->timing.v_display;
        
        if( vc->used_crtc[0] ) {
                si->crtc[0].rel_x = 0;
                si->crtc[0].rel_y = 0;
        } 
        
        if( vc->used_crtc[1] ) {
                si->crtc[1].rel_x = 0;
                si->crtc[1].rel_y = 0;
        }
        
        switch( mode->timing.flags & RADEON_MODE_MASK ) {
        case RADEON_MODE_COMBINE:
                // detect where second screen must be located and
                // adapt total visible area accordingly
                if( (mode->timing.flags & RADEON_MODE_POSITION_MASK) == RADEON_MODE_POSITION_HORIZONTAL ) {
                        vc->eff_width = 2 * mode->timing.h_display;
                        x = mode->timing.h_display;
                        y = 0;
                } else {
                        vc->eff_height = 2 * mode->timing.v_display;
                        x = 0;
                        y = mode->timing.v_display;
                }
                
                SHOW_FLOW( 3, "relative position of second screen: %d, %d", x, y );
                
                // set relative offset
                if( !vc->swap_displays ) {
                        si->crtc[1].rel_x = x;
                        si->crtc[1].rel_y = y;
                } else {
                        si->crtc[0].rel_x = x;
                        si->crtc[0].rel_y = y;
                }
                break;
                
        default:
                // else, ports are independant but show the same
                break;
        }
}


// mapping of internal TV standard code to public TV standard code
static const uint32 private2be[] = {
        0, 1, 3, 4, 103, 3/* PAL SCART - no public id, so I use PAL BDGHI */, 102 };

// check and execute tunnel settings command
status_t Radeon_CheckMultiMonTunnel( virtual_card *vc, display_mode *mode, 
        const display_mode *low, const display_mode *high, bool *isTunneled )
{
        if( (mode->timing.flags & RADEON_MODE_MULTIMON_REQUEST) != 0 &&
                (mode->timing.flags & RADEON_MODE_MULTIMON_REPLY) == 0 )
        {
                mode->timing.flags &= ~RADEON_MODE_MULTIMON_REQUEST;
                mode->timing.flags |= RADEON_MODE_MULTIMON_REPLY;
                
                // still process request, just in case someone set this flag
                // combination by mistake
                
                // TBD: disabled to shorten syslog 
                *isTunneled = true;
                return B_OK;
        }

        // check magic params
        if( mode->space != 0 || low->space != 0 || high->space != 0 
                || low->virtual_width != 0xffff || low->virtual_height != 0xffff
                || high->virtual_width != 0 || high->virtual_height != 0
                || mode->timing.pixel_clock != 0 
                || low->timing.pixel_clock != 'TKTK' || high->timing.pixel_clock != 'KTKT' )
        {
                *isTunneled = false;
                return B_OK;
        }
        
        *isTunneled = true;
        
        /*SHOW_FLOW( 1, "tunnel access code=%d, command=%d", 
                mode->h_display_start, mode->v_display_start );*/
        
        switch( mode->h_display_start ) {
        case ms_swap:
                switch( mode->v_display_start ) {
                case 0:
                        mode->timing.flags = vc->swap_displays;
                        return B_OK;
                        
                case 1:
                        vc->swap_displays = mode->timing.flags != 0;
                        vc->enforce_mode_change = true;
                        // write settings instantly
                        Radeon_WriteSettings( vc );
                        return B_OK;
                }
                break;
                
        case ms_use_laptop_panel:
                // we must refuse this setting if there is no laptop panel;
                // else, the preferences dialog would show this (useless) option
                if( (vc->connected_displays & dd_lvds) == 0 )
                        return B_ERROR;
                        
                switch( mode->v_display_start ) {
                case 0:
                        mode->timing.flags = vc->use_laptop_panel;
                        //SHOW_FLOW( 1, "get use_laptop_panel settings (%d)", mode->timing.flags );
                        return B_OK;
                        
                case 1:
                        vc->use_laptop_panel = mode->timing.flags != 0;
                        //SHOW_FLOW( 1, "set use_laptop_panel settings (%d)", vc->use_laptop_panel );
                        vc->enforce_mode_change = true;
                        Radeon_WriteSettings( vc );
                        return B_OK;
                }
                break;
                
        case ms_tv_standard:
                switch( mode->v_display_start ) {
                case 0:
                        mode->timing.flags = private2be[vc->tv_standard];
                        /*SHOW_FLOW( 1, "read tv_standard (internal %d, public %d)", 
                                vc->tv_standard, mode->timing.flags );*/
                        return B_OK;
                        
                case 1:
                        switch( mode->timing.flags ) {
                        case 0: vc->tv_standard = ts_off; break;
                        case 1: vc->tv_standard = ts_ntsc; break;
                        case 2: break; // ntsc j
                        case 3: vc->tv_standard = ts_pal_bdghi; break;
                        case 4: vc->tv_standard = ts_pal_m; break;
                        case 5: break; // pal n
                        case 6: break; // secam - I reckon not supported by hardware
                        case 101: break; // ntsc 443
                        case 102: vc->tv_standard = ts_pal_60; break;
                        case 103: vc->tv_standard = ts_pal_nc; break;
                        }
                        
                        SHOW_FLOW( 1, "set tv_standard (internal %d, public %d)", 
                                vc->tv_standard, mode->timing.flags );
                
                        vc->enforce_mode_change = true; 
                        Radeon_WriteSettings( vc );
                        return B_OK;
                        
                case 2: {
                        uint32 idx = mode->timing.flags;
                        
                        // we limit it explicetely to NTSC and PAL as all other
                        // modes are not fully implemented
                        if( idx < sizeof( private2be ) / sizeof( private2be[0] ) && 
                                idx < 3 ) {
                                mode->timing.flags = private2be[idx];
                                return B_OK;
                        } else
                                return B_ERROR;
                        }
                }
        }
        
        return B_ERROR;
}


// return true if both ports must be programmed
bool Radeon_NeedsSecondPort( display_mode *mode )
{
        switch( mode->timing.flags & RADEON_MODE_MASK ) {
        case RADEON_MODE_COMBINE:
                return true;
        default:
                return false;
        }
}


// return number of ports showing differents parts of frame buffer
bool Radeon_DifferentPorts( display_mode *mode )
{
        switch( mode->timing.flags & RADEON_MODE_MASK ) {
        case RADEON_MODE_COMBINE:
                return 2;
        default:
                return 1;
        }
}