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

        Part of Radeon accelerant
                
        CRTC <-> display routing
        
        This stuff is highly ASIC dependant and is probably the most ASIC-specific
        code of the entire project.
*/

#include "radeon_accelerant.h"
#include "mmio.h"
#include "dac_regs.h"
#include "fp_regs.h"
#include "crtc_regs.h"
#include "tv_out_regs.h"
#include "pll_regs.h"
#include "gpiopad_regs.h"
#include "pll_access.h"
#include "set_mode.h"


// read regs needed for display device routing
void Radeon_ReadMonitorRoutingRegs( 
        accelerator_info *ai, routing_regs *values )
{
        vuint8 *regs = ai->regs;
        
        values->dac_cntl = INREG( regs, RADEON_DAC_CNTL );
        values->dac_cntl2 = INREG( regs, RADEON_DAC_CNTL2 );
        values->crtc_ext_cntl = INREG( regs, RADEON_CRTC_EXT_CNTL );
        values->crtc2_gen_cntl = INREG( regs, RADEON_CRTC2_GEN_CNTL );
        values->disp_output_cntl = INREG( regs, RADEON_DISP_OUTPUT_CNTL );
        values->pixclks_cntl = Radeon_INPLL( ai->regs, ai->si->asic, RADEON_PIXCLKS_CNTL );
        values->vclk_ecp_cntl = Radeon_INPLL( ai->regs, ai->si->asic, RADEON_VCLK_ECP_CNTL );
        
        switch( ai->si->asic ) {
        case rt_rv100:
        case rt_rv200:  
        case rt_rv250:
        case rt_rv280:
        case rt_rs100:
        case rt_rs200:
        case rt_rs300:
                values->disp_hw_debug = INREG( regs, RADEON_DISP_HW_DEBUG );
                break;

        case rt_r200:
                values->disp_tv_out_cntl = INREG( regs, RADEON_DISP_TV_OUT_CNTL );
                break;

        case rt_r300:
        case rt_rv350:
        case rt_r350:
        case rt_rv380:
        case rt_r420:
                values->gpiopad_a = INREG( regs, RADEON_GPIOPAD_A );
                break;
                
        case rt_r100:
                break;
        }
        
        if( ai->si->asic > rt_r100 ) {  
                // register introduced after R100
                values->tv_dac_cntl = INREG( regs, RADEON_TV_DAC_CNTL );
        }
        
        if( IS_INTERNAL_TV_OUT( ai->si->tv_chip ))
                values->tv_master_cntl = INREG( regs, RADEON_TV_MASTER_CNTL );
        
        values->fp_gen_cntl = INREG( regs, RADEON_FP_GEN_CNTL );
        values->fp2_gen_cntl = INREG( regs, RADEON_FP2_GEN_CNTL );
}


// setup register contents to proper CRTC <-> display device mapping
void Radeon_CalcMonitorRouting( 
        accelerator_info *ai, const impactv_params *tv_parameters, routing_regs *values )
{
        display_device_e display_devices[2], total_devices, controlled_devices;
        
        if( ai->vc->used_crtc[0] )
                display_devices[0] = ai->si->crtc[0].chosen_displays;
        else
                display_devices[0] = dd_none;
                
        if( ai->vc->used_crtc[1] )
                display_devices[1] = ai->si->crtc[1].chosen_displays;
        else
                display_devices[1] = dd_none;
                
        total_devices = display_devices[0] | display_devices[1];
        controlled_devices = ai->vc->controlled_displays;

        // enable 8 bit DAC 
        // (could be moved to boot initialization)
        values->dac_cntl |= 
                RADEON_DAC_MASK_ALL | RADEON_DAC_VGA_ADR_EN | RADEON_DAC_8BIT_EN;
                
        // enable frame buffer access and extended CRTC counter
        // (again: something for boot init.)
        values->crtc_ext_cntl =
                RADEON_VGA_ATI_LINEAR | RADEON_XCRT_CNT_EN;
                
        // set VGA signal style (not sure whether this affects 
        // CRTC1 or CRT-DAC, so we better always set it)
        values->dac_cntl &= ~(RADEON_DAC_RANGE_CNTL_MASK | RADEON_DAC_BLANKING);
        values->dac_cntl |= RADEON_DAC_RANGE_CNTL_PS2;

        // disable all the magic CRTC shadowing
        values->fp_gen_cntl &= 
                ~(RADEON_FP_RMX_HVSYNC_CONTROL_EN |
                  RADEON_FP_DFP_SYNC_SEL |      
                  RADEON_FP_CRT_SYNC_SEL | 
                  RADEON_FP_CRTC_LOCK_8DOT |
                  RADEON_FP_USE_SHADOW_EN |
                  RADEON_FP_CRTC_USE_SHADOW_VEND |
                  RADEON_FP_CRT_SYNC_ALT);
        values->fp_gen_cntl |= 
                RADEON_FP_CRTC_DONT_SHADOW_VPAR |
                RADEON_FP_CRTC_DONT_SHADOW_HEND;
        
        // route VGA-DAC
        if( (total_devices & dd_crt) != 0 ) {
                int crtc_idx = (display_devices[1] & dd_crt) != 0;
                
                // the CRT_ON flag seems to directly affect the CRT-DAC, _not_ the CRTC1 signal
                values->crtc_ext_cntl |= RADEON_CRTC_CRT_ON;

                switch( ai->si->asic ) {
                case rt_rv100:
                case rt_rv200:
                case rt_rv250:
                case rt_rv280:
                case rt_rs100:
                case rt_rs200:
                case rt_rs300:
                        values->dac_cntl2 &= ~RADEON_DAC_CLK_SEL_MASK;
                        values->dac_cntl2 |= crtc_idx == 0 ? 0 : RADEON_DAC_CLK_SEL_CRTC2;
                        break;

                case rt_r200:
                case rt_r300:
                case rt_rv350:
                case rt_r350:
                case rt_rv380:
                case rt_r420:
                        values->disp_output_cntl &= ~RADEON_DISP_DAC_SOURCE_MASK;
                        values->disp_output_cntl |=
                                (crtc_idx == 0 ? 0 : RADEON_DISP_DAC_SOURCE_CRTC2);
                        break;
                
                case rt_r100:
                        break;
                }
                
        } else if( (controlled_devices & dd_crt) != 0 ) {
                values->crtc_ext_cntl &= ~RADEON_CRTC_CRT_ON;
        }
        

        if( (total_devices & (dd_tv_crt | dd_ctv | dd_stv)) != 0 ) {
                // power down TV-DAC
                // (but only if TV-Out _and_ TV-CRT is controlled by us)
                // this will be undone if needed
                values->tv_dac_cntl |= 
                        RADEON_TV_DAC_CNTL_RDACPD |
                        RADEON_TV_DAC_CNTL_GDACPD |
                        RADEON_TV_DAC_CNTL_BDACPD;
        }
        
        
        // set CRT mode of TV-DAC if needed
        // (doesn't work on r200, but there is no TV-DAC used for CRT anyway)
        if( (total_devices & dd_tv_crt) != 0 ) {
                // enable CRT via TV-DAC (ignored if TV-DAC is in TV-Out mode)
                values->crtc2_gen_cntl |= RADEON_CRTC2_CRT2_ON;

                values->dac_cntl2 &= ~RADEON_DAC2_CLK_SEL_MASK;
                values->dac_cntl2 |= RADEON_DAC2_CLK_SEL_CRT;

                // enable TV-DAC and set PS2 signal level
                values->tv_dac_cntl = 
                        RADEON_TV_DAC_CNTL_NBLANK |
                        RADEON_TV_DAC_CNTL_NHOLD |
                        RADEON_TV_DAC_CNTL_STD_PS2 |
                        (8 << RADEON_TV_DAC_CNTL_BGADJ_SHIFT) |
                        (2 << RADEON_TV_DAC_CNTL_DACADJ_SHIFT);
                
                // at least r300 needs magic bit set to switch between TV-CRT and TV-Out
                if (IS_R300_VARIANT)
                                values->gpiopad_a |= 1;
                                
        } else if( (controlled_devices & dd_tv_crt) != 0 ) {
                values->crtc2_gen_cntl &= ~RADEON_CRTC2_CRT2_ON;
        }
        
        values->skip_tv_dac = false;


        // disable forwarding data to TV-Out unit
        // (will be enabled on demand later on) 
        if( (controlled_devices & (dd_ctv | dd_stv)) != 0 )
                values->dac_cntl &= ~RADEON_DAC_TVO_EN;
                
        
        // set TV mode of TV-DAC if needed
        if( (total_devices & (dd_ctv | dd_stv)) != 0 ) {
                // see above
                values->dac_cntl2 &= ~RADEON_DAC2_CLK_SEL_MASK;
                values->dac_cntl2 |= RADEON_DAC2_CLK_SEL_TV;
                
                // at least r300 needs magic bit set to switch between TV-CRT and TV-Out
                if(IS_R300_VARIANT)
                                values->gpiopad_a &= ~1;
                
                
                // the TV-DAC itself is under control of the TV-Out code
                values->skip_tv_dac = true;
                
                if( !IS_INTERNAL_TV_OUT( ai->si->tv_chip )) {
                        // tell DAC to forward data to external chip
                        values->dac_cntl |= RADEON_DAC_TVO_EN;
                        
                        // set Output Linear Transform Unit as source
                        // (TODO: is this unit initialized properly?)
                        // disable overlay sync (could be a good idea to enable it)
                        // set 8 BPP mode
                        values->disp_output_cntl &= 
                                ~(RADEON_DISP_TV_SOURCE |
                                RADEON_DISP_TV_MODE_MASK |
                                RADEON_DISP_TV_YG_DITH_EN |
                                RADEON_DISP_TV_CBB_CRR_DITH_EN |
                                RADEON_DISP_TV_BIT_WIDTH |
                                RADEON_DISP_TV_SYNC_MODE_MASK |
                                RADEON_DISP_TV_SYNC_COLOR_MASK);
                        
                        // enable dithering
                        values->disp_output_cntl |= 
                                RADEON_DISP_TV_YG_DITH_EN |
                                RADEON_DISP_TV_CBB_CRR_DITH_EN;
                        
                        // set output data format       
                        values->disp_output_cntl |= tv_parameters->mode888 ? 
                                RADEON_DISP_TV_MODE_888 : RADEON_DISP_TV_MODE_565;
                                
                        switch( ai->si->asic ) {
                        case rt_r200:
                                // disable downfiltering and scaling, set RGB mode, 
                                // don't transmit overlay indicator;
                                // I don't really know whether this is a good choice
                                values->disp_tv_out_cntl &= 
                                        (RADEON_DISP_TV_OUT_YG_FILTER_MASK |
                                         RADEON_DISP_TV_OUT_YG_SAMPLE |
                                         RADEON_DISP_TV_OUT_CrR_FILTER_MASK |
                                         RADEON_DISP_TV_OUT_CrR_SAMPLE |
                                         RADEON_DISP_TV_OUT_CbB_FILTER_MASK |
                                         RADEON_DISP_TV_OUT_CbB_SAMPLE |
                                         RADEON_DISP_TV_SUBSAMPLE_CNTL_MASK |
                                         RADEON_DISP_TV_H_DOWNSCALE |
                                         RADEON_DISP_TV_COLOR_SPACE |
                                         RADEON_DISP_TV_DITH_MODE |
                                         RADEON_DISP_TV_DATA_ZERO_SEL |
                                         RADEON_DISP_TV_CLKO_SEL |
                                         RADEON_DISP_TV_CLKO_OUT_EN |
                                         RADEON_DISP_TV_DOWNSCALE_CNTL);
                                         
                                // enable TVOCLKO (is this needed?)
                                values->disp_tv_out_cntl |= RADEON_DISP_TV_CLKO_OUT_EN;
                                break;
                        
                        default:
                                ;
                        }
                }
                
        } else if( (controlled_devices & (dd_ctv | dd_stv)) != 0 ) {
                if( IS_INTERNAL_TV_OUT( ai->si->tv_chip )) {
                        // disable clock of TV-out units
                        values->tv_master_cntl = 
                                RADEON_TV_MASTER_CNTL_TV_ASYNC_RST | 
                                RADEON_TV_MASTER_CNTL_CRT_ASYNC_RST |
                                RADEON_TV_MASTER_CNTL_TV_FIFO_ASYNC_RST | 
                                RADEON_TV_MASTER_CNTL_TVCLK_ALWAYS_ONb;
                }
        }
        
        // choose CRTC for TV-DAC
        if( (total_devices & (dd_tv_crt | dd_ctv | dd_stv)) != 0 ) {
                int crtc_idx = (display_devices[1] & (dd_tv_crt | dd_ctv | dd_stv)) != 0;
                
                switch( ai->si->asic ) {
                case rt_rv100:
                case rt_rv200:  
                case rt_rv250:
                case rt_rv280:
                case rt_rs100:
                case rt_rs200:
                case rt_rs300:
                                        values->disp_hw_debug &= ~RADEON_CRT2_DISP1_SEL;
                        // warning: meaning is wrong way around - 0 means crtc2, 1 means crtc1
                        values->disp_hw_debug |= crtc_idx == 0 ? RADEON_CRT2_DISP1_SEL : 0;
                        break;
                
                case rt_r200:
                        // TV-Out data comes directly from CRTC (i.e. with Linear Transform Unit)
                        values->disp_output_cntl |= RADEON_DISP_TV_SOURCE;
                        // choose CRTC
                        values->disp_tv_out_cntl &= ~RADEON_DISP_TV_PATH_SRC;
                        values->disp_tv_out_cntl |= crtc_idx == 0 ? 0 : RADEON_DISP_TV_PATH_SRC;
                        break;

                case rt_r300:
                case rt_rv350:
                case rt_r350:
                case rt_rv380:
                case rt_r420:
                        values->disp_output_cntl &= ~RADEON_DISP_TVDAC_SOURCE_MASK;
                        values->disp_output_cntl |=
                                crtc_idx == 0 ? 0 : RADEON_DISP_TVDAC_SOURCE_CRTC2;
                        break;
                        
                case rt_r100:
                        break;
                }
        }
        
        // choose clock source for (internal) TV-out unit
        if( (total_devices & (dd_ctv | dd_stv)) != 0 ) {
                int crtc_idx = (display_devices[1] & (dd_ctv | dd_stv)) != 0;
                
                values->pixclks_cntl &= ~RADEON_PIXCLK_TV_SRC_SEL_MASK;
                values->pixclks_cntl |= crtc_idx == 0 ?
                        RADEON_PIXCLK_TV_SRC_SEL_PIXCLK : RADEON_PIXCLK_TV_SRC_SEL_PIX2CLK;
        }

        // choose CRTC clock source;
        // normally, CRTC1 uses PLL1 and CRTC2 uses PLL2, but if an external TV-Out
        // chip is used, the clock is retrieved from this chip to stay in perfect sync
        if( (display_devices[0] & (dd_ctv | dd_stv)) != 0
                && !IS_INTERNAL_TV_OUT( ai->si->tv_chip ))
        {
                // select BYTCLK input pin as pixel src
                values->vclk_ecp_cntl &=
                        ~(RADEON_VCLK_ECP_CNTL_BYTE_CLK_POST_DIV_MASK | RADEON_VCLK_SRC_SEL_MASK);
                
                values->vclk_ecp_cntl |= RADEON_VCLK_SRC_BYTE_CLK;
                values->vclk_ecp_cntl |= 0 << RADEON_VCLK_ECP_CNTL_BYTE_CLK_POST_DIV_SHIFT;
                
                // disable clock if pixel format in CRTC_GEN_CNTL is zero;
                // disable (DAC?) during blank
                values->vclk_ecp_cntl |= RADEON_PIXCLK_ALWAYS_ONb | RADEON_PIXCLK_DAC_ALWAYS_ONb;

        } else {
                // select PLL as pixel clock
                values->vclk_ecp_cntl &= ~RADEON_VCLK_SRC_SEL_MASK;
                values->vclk_ecp_cntl |= RADEON_VCLK_SRC_PPLL_CLK;
                
                // disable clock if pixel format in CRTC_GEN_CNTL is zero
                values->vclk_ecp_cntl |= RADEON_PIXCLK_ALWAYS_ONb;
        }

        values->pixclks_cntl &= ~RADEON_PIX2CLK_SRC_SEL_MASK;
        if( (display_devices[1] & (dd_ctv | dd_stv)) != 0
                && !IS_INTERNAL_TV_OUT( ai->si->tv_chip ))
        {
                // r200 spec misses everything regarding second CRTC, so
                // this is guessing
                values->pixclks_cntl |= 2;
        } else
                values->pixclks_cntl |= RADEON_PIX2CLK_SRC_SEL_P2PLL_CLK;
        
        // choose CRTC for flat panel
        if( (total_devices & (dd_lvds | dd_dvi)) != 0 ) {
                int crtc_idx = (display_devices[1] & (dd_lvds | dd_dvi)) != 0;
                
                values->fp_gen_cntl &= ~RADEON_FP_SEL_CRTC2;
                values->fp_gen_cntl |= crtc_idx == 0 ? 0 : RADEON_FP_SEL_CRTC2;
        }

        // enable/disable RMX for crtc1 if there is a flat panel
        // (TODO: this doesn't seem to work)
        // !!! makes trouble on Radeon 9200 Mobility !??
        if( (display_devices[1] & (dd_lvds | dd_dvi)) != 0 ) {
                values->disp_output_cntl &= ~RADEON_DISP_DAC_SOURCE_MASK;
                values->disp_output_cntl |= RADEON_DISP_DAC_SOURCE_RMX;
        }

        
        // choose CRTC for secondary flat panel
        if( (total_devices & dd_dvi_ext) != 0 ) {
                int crtc_idx = (display_devices[1] & (dd_dvi_ext)) != 0;

                // TODO: this list looks a bit magic/wrong for me; I reckon ATI moved the
                // bit starting with ASIC xxx, but I have no specs to verify that
                switch( ai->si->asic ) {
                case rt_r200:
                case rt_r300:
                case rt_r350:
                case rt_rv350:  
                case rt_rv380:
                case rt_r420:
                        values->fp2_gen_cntl &= ~RADEON_FP2_SOURCE_SEL_CRTC2;
                    values->fp2_gen_cntl |= 
                        crtc_idx == 0 ? 0 : RADEON_FP2_SOURCE_SEL_CRTC2;
                    break;
                    
                default:
                        values->fp2_gen_cntl &= ~RADEON_FP2_SRC_SEL_CRTC2;
                        values->fp2_gen_cntl |= 
                                crtc_idx == 0 ? 0 : RADEON_FP2_SRC_SEL_CRTC2;
                }
        }
}

void Radeon_ProgramMonitorRouting( 
        accelerator_info *ai, routing_regs *values )
{
        vuint8 *regs = ai->regs;
        
        OUTREG( regs, RADEON_DAC_CNTL, values->dac_cntl );
        OUTREG( regs, RADEON_DAC_CNTL2, values->dac_cntl2 );
        OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, values->crtc2_gen_cntl,
                ~RADEON_CRTC2_CRT2_ON );
        OUTREG( regs, RADEON_DISP_OUTPUT_CNTL, values->disp_output_cntl );

        switch( ai->si->asic ) {
        case rt_rv100:
        case rt_rv200:  
        case rt_rv250:
        case rt_rv280:
        case rt_rs100:
        case rt_rs200:
        case rt_rs300:
                OUTREG( regs, RADEON_DISP_HW_DEBUG, values->disp_hw_debug );
                break;
                
        case rt_r200:
                OUTREG( regs, RADEON_DISP_TV_OUT_CNTL, values->disp_tv_out_cntl );
                break;

        case rt_r300:
        case rt_rv350:
        case rt_r350:
        case rt_rv380:
        case rt_r420:
                OUTREGP( regs, RADEON_GPIOPAD_A, values->gpiopad_a, ~1 );
                break;
                
        case rt_r100:
                break;
        }

        if( ai->si->asic > rt_r100 ) {
                // register introduced after R100;
                // only set it when necessary (more precisely: if TV-Out is used,
                // this register is set by the TV-Out code)
                if( !values->skip_tv_dac )
                        OUTREG( regs, RADEON_TV_DAC_CNTL, values->tv_dac_cntl );
        }
        
        if( IS_INTERNAL_TV_OUT( ai->si->tv_chip ))
                OUTREG( regs, RADEON_TV_MASTER_CNTL, values->tv_master_cntl );
        
        OUTREGP( regs, RADEON_FP_GEN_CNTL, values->fp_gen_cntl, ~(
                RADEON_FP_SEL_CRTC2 |
                RADEON_FP_RMX_HVSYNC_CONTROL_EN |
                RADEON_FP_DFP_SYNC_SEL |        
                RADEON_FP_CRT_SYNC_SEL | 
                RADEON_FP_CRTC_LOCK_8DOT |
                RADEON_FP_USE_SHADOW_EN |
                RADEON_FP_CRTC_USE_SHADOW_VEND |
                RADEON_FP_CRT_SYNC_ALT |
                RADEON_FP_CRTC_DONT_SHADOW_VPAR |
                RADEON_FP_CRTC_DONT_SHADOW_HEND ));
                
                
        OUTREGP( regs, RADEON_FP2_GEN_CNTL, values->fp2_gen_cntl, 
                ~(RADEON_FP2_SOURCE_SEL_CRTC2 | RADEON_FP2_SRC_SEL_CRTC2 ));

        if( ai->vc->used_crtc[0] ) {
                Radeon_OUTPLLP( ai->regs, ai->si->asic, 
                        RADEON_VCLK_ECP_CNTL, values->vclk_ecp_cntl, 
                        ~RADEON_VCLK_SRC_SEL_MASK );
        }
        
        if( ai->vc->used_crtc[1] ) {
                Radeon_OUTPLLP( ai->regs, ai->si->asic, 
                        RADEON_PIXCLKS_CNTL, values->pixclks_cntl, 
                        ~RADEON_PIX2CLK_SRC_SEL_MASK );
        }
        
        Radeon_OUTPLLP( ai->regs, ai->si->asic, 
                RADEON_PIXCLKS_CNTL, values->pixclks_cntl, 
                ~RADEON_PIXCLK_TV_SRC_SEL_MASK );

        // enable/disable CRTC1
        if( ai->vc->assigned_crtc[0] ) {
                uint32 crtc_gen_cntl;
                
                crtc_gen_cntl = INREG( regs, RADEON_CRTC_GEN_CNTL );
                
                if( ai->vc->used_crtc[0] ) {
                        crtc_gen_cntl |= RADEON_CRTC_EN;
                } else {
                        crtc_gen_cntl &= ~RADEON_CRTC_EN;
                        crtc_gen_cntl &= ~RADEON_CRTC_PIX_WIDTH_MASK;
                }
                        
                OUTREGP( regs, RADEON_CRTC_GEN_CNTL, crtc_gen_cntl, 
                        ~(RADEON_CRTC_PIX_WIDTH_MASK | RADEON_CRTC_EN) );
        }

        // enable/disable CRTC2
        if( ai->vc->assigned_crtc[1] ) {
                uint32 crtc2_gen_cntl;
                
                crtc2_gen_cntl = INREG( regs, RADEON_CRTC2_GEN_CNTL );
                
                if( ai->vc->used_crtc[1] ) {
                        crtc2_gen_cntl |= RADEON_CRTC2_EN;
                } else {
                        crtc2_gen_cntl &= ~RADEON_CRTC2_EN;
                        crtc2_gen_cntl &= ~RADEON_CRTC2_PIX_WIDTH_MASK;
                }
                        
                OUTREGP( regs, RADEON_CRTC2_GEN_CNTL, crtc2_gen_cntl, 
                        ~(RADEON_CRTC2_PIX_WIDTH_MASK | RADEON_CRTC2_EN) );
        }
                
        // XFree says that crtc_ext_cntl must be restored after CRTC2 in dual-screen mode
        OUTREGP( regs, RADEON_CRTC_EXT_CNTL, values->crtc_ext_cntl,
                RADEON_CRTC_VSYNC_DIS |
                RADEON_CRTC_HSYNC_DIS |
                RADEON_CRTC_DISPLAY_DIS );
}


// internal version of SetupDefaultMonitorRouting;
// input and output are written to local variables
static void assignDefaultMonitorRoute( 
        accelerator_info *ai,
        display_device_e display_devices, int whished_num_heads, bool use_laptop_panel,
        display_device_e *crtc1, display_device_e *crtc2 )
{
        virtual_card *vc = ai->vc;
        display_device_e crtc1_displays = 0, crtc2_displays = 0;
        
        SHOW_FLOW( 2, "display_devices=%x, whished_num_heads=%d", 
                display_devices, whished_num_heads );
        
        // restrict to allowed devices  
        display_devices &= ai->vc->controlled_displays;
        
        // if CRTC1 is not ours, we cannot use flat panels
        if( !ai->vc->assigned_crtc[0] ) {
                display_devices &= ~(dd_lvds | dd_dvi);
        }
        
        SHOW_FLOW( 2, "after restriction: %x", display_devices );
        
        // flat panels get always connected to CRTC1 because its RMX unit
        if( (display_devices & dd_lvds) != 0 ) {
                // if user requests it, laptop panels are always used
                if( use_laptop_panel ) {
                        crtc1_displays |= dd_lvds;
                        
                } else {
                        // if he doesn't request it, we try to not use it
                        display_device_e tmp_crtc1, tmp_crtc2;
                        int effective_num_heads;
                        
                        // determine routing with laptop panel ignored
                        assignDefaultMonitorRoute( ai, display_devices & ~dd_lvds, 
                                whished_num_heads, use_laptop_panel, &tmp_crtc1, &tmp_crtc2 );

                        effective_num_heads = (tmp_crtc1 != 0) + (tmp_crtc2 != 0);
                        
                        // only use laptop panel if we cannot satisfy the requested 
                        // number of heads without it
                        if( effective_num_heads < whished_num_heads )
                                crtc1_displays |= dd_lvds;
                }
                
        } else if( (display_devices & dd_dvi) != 0 )
                crtc1_displays |= dd_dvi;
                
        // TV-Out gets always connected to crtc2...
        if( (display_devices & dd_stv) != 0 )
                crtc2_displays |= dd_stv;
        else if( (display_devices & dd_ctv) != 0 )
                crtc2_displays |= dd_ctv;
                
        // ...but if there is no crtc2, they win on crtc1;
        // if the user connects both a flat panel and a TV, he usually 
        // wants to use the TV
        if( !vc->assigned_crtc[1] && crtc2_displays != 0 ) {
                crtc1_displays = crtc2_displays;
                crtc2_displays = dd_none;
        }
                
        // if internal TV-Out is used, the DAC cannot drive a CRT at the same time
        if( IS_INTERNAL_TV_OUT( ai->si->tv_chip ) && (display_devices & (dd_stv | dd_ctv)) != 0 )
                display_devices &= ~dd_tv_crt;
                
        // CRT on CRT-DAC gets any spare CRTC;
        // if there is none, it can share CRTC with TV-Out;
        // this sharing may be dangerous as TV-Out uses strange timings, so
        // we should perhaps forbid sharing
        if( (display_devices & dd_crt) != 0 ) {
                if( crtc1_displays == 0 && vc->assigned_crtc[0] )
                        crtc1_displays |= dd_crt;
                else if( ai->si->num_crtc > 1 && crtc2_displays == 0 && vc->assigned_crtc[1] )
                        crtc2_displays |= dd_crt;
                else if( (crtc1_displays & ~(dd_stv | dd_ctv)) == 0 && vc->assigned_crtc[0] )
                        crtc1_displays |= dd_crt;
                else if( ai->si->num_crtc > 1 && (crtc2_displays & ~(dd_stv | dd_ctv)) == 0 && vc->assigned_crtc[1] )
                        crtc2_displays |= dd_crt;
        }
        
        // same applies to CRT on TV-DAC;
        // if we cannot find a CRTC, we could clone the content of the CRT-DAC,
        // but I doubt that you really want two CRTs showing the same
        if( (display_devices & dd_tv_crt) != 0 ) {
                if( crtc1_displays == 0 && vc->assigned_crtc[0] )
                        crtc1_displays |= dd_tv_crt;
                else if( ai->si->num_crtc > 1 && crtc2_displays == 0 && vc->assigned_crtc[1] )
                        crtc2_displays |= dd_tv_crt;
                else if( (crtc1_displays & ~(dd_stv | dd_ctv)) == 0 && vc->assigned_crtc[0] )
                        crtc1_displays |= dd_tv_crt;
                else if( ai->si->num_crtc > 1 && (crtc2_displays & ~(dd_stv | dd_ctv)) == 0 && vc->assigned_crtc[1] )
                        crtc2_displays |= dd_tv_crt;
        }

        if( (display_devices & dd_dvi_ext) != 0 )
                crtc2_displays |= dd_dvi_ext;

        SHOW_FLOW( 2, "CRTC1: 0x%x, CRTC2: 0x%x", crtc1_displays, crtc2_displays );
        
        *crtc1 = crtc1_displays;
        *crtc2 = crtc2_displays;
}

// Setup sensible default monitor routing
// whished_num_heads - number of independant heads current display mode would need
// use_laptop_panel - if true, always use laptop panel
void Radeon_SetupDefaultMonitorRouting( 
        accelerator_info *ai, int whished_num_heads, bool use_laptop_panel )
{
        virtual_card *vc = ai->vc;
        shared_info *si = ai->si;
        display_device_e display_devices = vc->connected_displays;

        if (ai->si->settings.force_lcd) {
                use_laptop_panel = true;
                SHOW_FLOW0( 2,  "LCD Forced Used by Kernel Settings");
        }
        
        SHOW_FLOW( 2, "display_devices=%x, whished_num_heads=%d, use_laptop_panel=%d", 
                display_devices, whished_num_heads, use_laptop_panel );
                
        // ignore TV if standard is set to "off"
        if( vc->tv_standard == ts_off )
                display_devices &= ~(dd_ctv | dd_stv);
        
        assignDefaultMonitorRoute( 
                ai, display_devices, whished_num_heads, use_laptop_panel,
                &si->crtc[0].chosen_displays, &si->crtc[1].chosen_displays );
                
/*      si->crtc[0].chosen_displays = dd_none;
        si->crtc[1].chosen_displays = dd_tv_crt;*/
                
        /*vc->used_crtc[0] = si->crtc[0].chosen_displays != dd_none;
        vc->used_crtc[1] = si->crtc[1].chosen_displays != dd_none;*/
                
        SHOW_FLOW( 2, "num_crtc: %d, CRTC1 (%s): 0x%x, CRTC2 (%s): 0x%x", 
                si->num_crtc,
                vc->assigned_crtc[0] ? "assigned" : "not assigned", si->crtc[0].chosen_displays, 
                vc->assigned_crtc[0] ? "assigned" : "not assigned", si->crtc[1].chosen_displays );
}