/* Copyright (c) 2002-2004, Thomas Kurschel Part of Radeon accelerant Sets display modes, colour palette and handles DPMS */ #include "GlobalData.h" #include "generic.h" #include #include "radeon_regs.h" #include "mmio.h" #include "crtc_regs.h" #include #include "crtc_regs.h" #include "overlay_regs.h" #include "rbbm_regs.h" #include "dac_regs.h" #include "fp_regs.h" #include "gpiopad_regs.h" #include "pll_regs.h" #include "tv_out_regs.h" #include "config_regs.h" #include "ddc_regs.h" #include "pll_access.h" #include "theatre_regs.h" #include "set_mode.h" #include "ddc.h" #include "set_mode.h" #include status_t Radeon_SetMode( accelerator_info *ai, crtc_info *crtc, display_mode *mode, impactv_params *tv_params ); // round virtual width up to next valid size uint32 Radeon_RoundVWidth( int virtual_width, int bpp ) { // we have to make both the CRTC and the accelerator happy: // - the CRTC wants virtual width in pixels to be a multiple of 8 // - the accelerator expects width in bytes to be a multiple of 64 // to put that together, width (in bytes) must be a multiple of the least // common nominator of bytes-per-pixel*8 (CRTC) and 64 (accelerator); // if bytes-per-pixel is a power of two and less than 8, the LCM is 64; // almost all colour depth satisfy that apart from 24 bit; in this case, // the LCM is 64*3=192 // after dividing by bytes-per-pixel we get pixels: in first case, // width must be multiple of 64/bytes-per-pixel; in second case, // width must be multiple of 64*3/3=64 if( bpp != 3 ) return (virtual_width + 64/bpp - 1) & ~(64/bpp - 1); else return (virtual_width + 63) & ~63; } // list of registers that must be reset before display mode switch // to avoid interferences static struct { uint16 reg; uint32 val; } common_regs[] = { { RADEON_OVR_CLR, 0 }, { RADEON_OVR_WID_LEFT_RIGHT, 0 }, { RADEON_OVR_WID_TOP_BOTTOM, 0 }, { RADEON_OV0_SCALE_CNTL, 0 }, // disable overlay { RADEON_SUBPIC_CNTL, 0 }, { RADEON_I2C_CNTL_1, 0 }, }; static void Radeon_InitCommonRegs( accelerator_info *ai ) { vuint8 *regs = ai->regs; uint i; for( i = 0; i < sizeof( common_regs) / sizeof( common_regs[0] ); ++i ) OUTREG( regs, common_regs[i].reg, common_regs[i].val ); // enable extended display modes OUTREGP( regs, RADEON_CRTC_GEN_CNTL, RADEON_CRTC_EXT_DISP_EN, ~RADEON_CRTC_EXT_DISP_EN ); // disable flat panel auto-centering // (if we have a CRT on CRTC1, this must be disabled; // if we have a flat panel on CRTC1, we setup CRTC manually, not // using the auto-centre, automatic-sync-override magic) OUTREG( regs, RADEON_CRTC_MORE_CNTL, 0 ); } // set display mode of one head; // port restrictions, like fixed-sync TFTs connected to it, are taken care of status_t Radeon_SetMode( accelerator_info *ai, crtc_info *crtc, display_mode *mode, impactv_params *tv_params ) { virtual_card *vc = ai->vc; shared_info *si = ai->si; vuint8 *regs = ai->regs; int format; int bpp; display_device_e disp_devices; fp_info *fp_info; crtc_regs crtc_values; pll_regs pll_values; fp_regs fp_values; impactv_regs impactv_values; uint32 surface_cntl; bool internal_tv_encoder; pll_dividers dividers; crtc->mode = *mode; // don't destroy passed values, use our copy instead mode = &crtc->mode; disp_devices = crtc->chosen_displays; fp_info = &si->flatpanels[crtc->flatpanel_port]; // if using an flat panel or LCD, maximum resolution // is determined by the physical resolution; // also, all timing is fixed if( (disp_devices & (dd_lvds | dd_dvi | dd_dvi_ext)) != 0 ) { SHOW_FLOW0( 0, "requested resolution higher than native panel" ); if( mode->timing.h_display > fp_info->panel_xres ) mode->timing.h_display = fp_info->panel_xres; if( mode->timing.v_display > fp_info->panel_yres ) mode->timing.v_display = fp_info->panel_yres; if( (disp_devices & dd_dvi_ext) != 0 ) { SHOW_FLOW0( 0, "requested resolution less than second native panel" ); if( mode->timing.h_display < fp_info->panel_xres ) mode->timing.h_display = fp_info->panel_xres; if( mode->timing.v_display < fp_info->panel_yres ) mode->timing.v_display = fp_info->panel_yres; //TODO at this point we know we are going to do centered timing //need to set flags to a. blank the unused memory, b.center screen //for now it's in the top corner, and surrounded by garbage. // although if the DVI panels are the same size and we are cloning // we can switch the FP2 source to RMX, and drive both screens from // the RMX unit. } mode->timing.h_total = mode->timing.h_display + fp_info->h_blank; mode->timing.h_sync_start = mode->timing.h_display + fp_info->h_over_plus; mode->timing.h_sync_end = mode->timing.h_sync_start + fp_info->h_sync_width; mode->timing.v_total = mode->timing.v_display + fp_info->v_blank; mode->timing.v_sync_start = mode->timing.v_display + fp_info->v_over_plus; mode->timing.v_sync_end = mode->timing.v_sync_start + fp_info->v_sync_width; mode->timing.pixel_clock = fp_info->dot_clock; } // TV-out supports at most 1024x768 if( (disp_devices & (dd_ctv | dd_stv)) != 0 ) { if( mode->timing.h_display > 1024 ) mode->timing.h_display = 1024; if( mode->timing.v_display > 768 ) mode->timing.v_display = 768; } // if using TV-Out, the timing of the source signal must be tweaked to // get proper timing internal_tv_encoder = IS_INTERNAL_TV_OUT( si->tv_chip ); // we need higher accuracy then Be thought of mode->timing.pixel_clock *= 1000; // TV stuff must be done first as it tweaks the display mode if( (disp_devices & (dd_ctv | dd_stv)) != 0 ) { display_mode tweaked_mode; Radeon_CalcImpacTVParams( &si->pll, tv_params, vc->tv_standard, internal_tv_encoder, mode, &tweaked_mode ); *mode = tweaked_mode; } Radeon_GetFormat( mode->space, &format, &bpp ); vc->bpp = bpp; vc->datatype = format; // time to read original register content // lock hardware so noone bothers us Radeon_WaitForIdle( ai, true ); if( (disp_devices & (dd_dvi | dd_lvds | dd_dvi_ext)) != 0 ) { if( crtc->crtc_idx == 0 ) Radeon_ReadRMXRegisters( ai, &fp_values ); Radeon_ReadFPRegisters( ai, &fp_values ); } if( (disp_devices & (dd_ctv | dd_stv)) != 0 ) { // some register's content isn't created from scratch but // only modified, so we need the original content first if( internal_tv_encoder ) Radeon_InternalTVOutReadRegisters( ai, &impactv_values ); else Radeon_TheatreReadTVRegisters( ai, &impactv_values ); } // calculate all hardware register values Radeon_CalcCRTCRegisters( ai, crtc, mode, &crtc_values ); surface_cntl = RADEON_SURF_TRANSLATION_DIS; if( (disp_devices & (dd_ctv | dd_stv)) != 0 ) { Radeon_CalcImpacTVRegisters( ai, mode, tv_params, &impactv_values, crtc->crtc_idx, internal_tv_encoder, vc->tv_standard, disp_devices ); } if( (disp_devices & (dd_stv | dd_ctv)) == 0 ) Radeon_CalcCRTPLLDividers( &si->pll, mode, ÷rs ); else dividers = tv_params->crt_dividers; if( (disp_devices & dd_lvds) != 0 && si->flatpanels[0].fixed_dividers ) { SHOW_FLOW0( 0, "Using fixed dividers for laptop panel" ); dividers.feedback = si->flatpanels[0].feedback_div; dividers.post_code = si->flatpanels[0].post_div; dividers.ref = si->flatpanels[0].ref_div; } Radeon_CalcPLLRegisters( mode, ÷rs, &pll_values ); // for first CRTC1, we need to setup RMX properly if( crtc->crtc_idx == 0 ) Radeon_CalcRMXRegisters( fp_info, mode, (disp_devices & (dd_lvds | dd_dvi | dd_dvi_ext)) != 0, &fp_values ); if( (disp_devices & (dd_lvds | dd_dvi | dd_dvi_ext)) != 0 ) Radeon_CalcFPRegisters( ai, crtc, fp_info, &crtc_values, &fp_values ); // we don't use pixel clock anymore, so it can be reset to Be's kHz mode->timing.pixel_clock /= 1000; // write values to registers Radeon_InitCommonRegs( ai ); Radeon_ProgramCRTCRegisters( ai, crtc->crtc_idx, &crtc_values ); OUTREG( regs, RADEON_SURFACE_CNTL, surface_cntl ); if( crtc->crtc_idx == 0 ) Radeon_ProgramRMXRegisters( ai, &fp_values ); if( (disp_devices & (dd_lvds | dd_dvi | dd_dvi_ext)) != 0 ) Radeon_ProgramFPRegisters( ai, crtc, fp_info, &fp_values ); //if( mode->timing.pixel_clock ) Radeon_ProgramPLL( ai, crtc->crtc_idx, &pll_values ); if( (disp_devices & (dd_ctv | dd_stv)) != 0 ) { if( internal_tv_encoder ) Radeon_InternalTVOutProgramRegisters( ai, &impactv_values ); else Radeon_TheatreProgramTVRegisters( ai, &impactv_values ); } // spit out some debug stuff in a radeontool stylee SHOW_FLOW0( 0, "" ); SHOW_FLOW( 0, "RADEON_DAC_CNTL %08X ", INREG( regs, RADEON_DAC_CNTL )); SHOW_FLOW( 0, "RADEON_DAC_CNTL2 %08X ", INREG( regs, RADEON_DAC_CNTL2 )); SHOW_FLOW( 0, "RADEON_TV_DAC_CNTL %08X ", INREG( regs, RADEON_TV_DAC_CNTL )); SHOW_FLOW( 0, "RADEON_DISP_OUTPUT_CNTL %08X ", INREG( regs, RADEON_DISP_OUTPUT_CNTL )); SHOW_FLOW( 0, "RADEON_AUX_SC_CNTL %08X ", INREG( regs, RADEON_AUX_SC_CNTL )); SHOW_FLOW( 0, "RADEON_CRTC_EXT_CNTL %08X ", INREG( regs, RADEON_CRTC_EXT_CNTL )); SHOW_FLOW( 0, "RADEON_CRTC_GEN_CNTL %08X ", INREG( regs, RADEON_CRTC_GEN_CNTL )); SHOW_FLOW( 0, "RADEON_CRTC2_GEN_CNTL %08X ", INREG( regs, RADEON_CRTC2_GEN_CNTL )); SHOW_FLOW( 0, "RADEON_DISP_MISC_CNTL %08X ", INREG( regs, RADEON_DISP_MISC_CNTL )); SHOW_FLOW( 0, "RADEON_FP_GEN_CNTL %08X ", INREG( regs, RADEON_FP_GEN_CNTL )); SHOW_FLOW( 0, "RADEON_FP2_GEN_CNTL %08X ", INREG( regs, RADEON_FP2_GEN_CNTL )); SHOW_FLOW( 0, "RADEON_LVDS_GEN_CNTL %08X ", INREG( regs, RADEON_LVDS_GEN_CNTL )); SHOW_FLOW( 0, "RADEON_TMDS_PLL_CNTL %08X ", INREG( regs, RADEON_TMDS_PLL_CNTL )); SHOW_FLOW( 0, "RADEON_TMDS_TRANSMITTER_CNTL %08X ", INREG( regs, RADEON_TMDS_TRANSMITTER_CNTL )); SHOW_FLOW( 0, "RADEON_FP_H_SYNC_STRT_WID %08X ", INREG( regs, RADEON_FP_H_SYNC_STRT_WID )); SHOW_FLOW( 0, "RADEON_FP_V_SYNC_STRT_WID %08X ", INREG( regs, RADEON_FP_V_SYNC_STRT_WID )); SHOW_FLOW( 0, "RADEON_FP_H2_SYNC_STRT_WID %08X ", INREG( regs, RADEON_FP_H2_SYNC_STRT_WID )); SHOW_FLOW( 0, "RADEON_FP_V2_SYNC_STRT_WID %08X ", INREG( regs, RADEON_FP_V2_SYNC_STRT_WID )); // spit end crtc->active_displays = disp_devices; // programming is over, so hardware can be used again RELEASE_BEN( si->cp.lock ); // overlay must be setup again after modeswitch (whoever was using it) // TBD: this won't work if another virtual card was using it, // but currently, virtual cards don't work anyway... si->active_overlay.crtc_idx = -1; return B_OK; } // enable or disable VBlank interrupts void Radeon_EnableIRQ( accelerator_info *ai, bool enable ) { shared_info *si = ai->si; uint32 int_cntl, int_mask; int_cntl = INREG( ai->regs, RADEON_GEN_INT_CNTL ); int_mask = RADEON_CRTC_VBLANK_MASK | (si->num_crtc > 1 ? RADEON_CRTC2_VBLANK_MASK : 0); if( enable ) int_cntl |= int_mask; else int_cntl &= ~int_mask; OUTREG( ai->regs, RADEON_GEN_INT_CNTL, int_cntl ); if( enable ) { // on enable, we have to acknowledge all IRQs as the graphics card // waits for that before it issues further IRQs OUTREG( ai->regs, RADEON_GEN_INT_STATUS, int_cntl ); } si->enable_virtual_irq = enable; } // public function: set display mode status_t SET_DISPLAY_MODE( display_mode *mode_in ) { virtual_card *vc = ai->vc; shared_info *si = ai->si; display_mode bounds, mode; mode = bounds = *mode_in; ACQUIRE_BEN( si->engine.lock ); SHOW_FLOW( 2, "width=%d, height=%d", mode.timing.h_display, mode.timing.v_display ); // check mode and tweak parameters so we can program hardware // without any further checks if( PROPOSE_DISPLAY_MODE( &mode, &bounds, &bounds ) == B_ERROR ) { SHOW_ERROR0( 2, "invalid mode" ); RELEASE_BEN( si->engine.lock ); return B_ERROR; } // already done by propose_display_mode, but it was undone on return; // do this before equality check to recognize changes of multi-monitor mode Radeon_DetectMultiMode( vc, &mode ); // mode switches can take quite long and are visible, // so avoid them if possible if( memcmp( &mode, &vc->mode, sizeof( display_mode )) == 0 && !vc->enforce_mode_change ) { RELEASE_BEN( si->engine.lock ); return B_OK; } // this flag was set when some internal parameter has changed that // affects effective display mode vc->enforce_mode_change = false; // make sure, we don't get disturbed //Radeon_Finish( ai ); Radeon_EnableIRQ( ai, false ); // free cursor and framebuffer memory { radeon_free_mem fm; fm.magic = RADEON_PRIVATE_DATA_MAGIC; fm.memory_type = mt_local; fm.global = true; if( vc->cursor.mem_handle ) { fm.handle = vc->cursor.mem_handle; ioctl( ai->fd, RADEON_FREE_MEM, &fm ); } if( vc->fb_mem_handle ) { fm.handle = vc->fb_mem_handle; ioctl( ai->fd, RADEON_FREE_MEM, &fm ); } } memcpy( &vc->mode, &mode, sizeof( display_mode )); // verify hardware restrictions *after* saving mode // e.g. if you want a span mode but have one monitor disconnected, // configuration shouldn't be touched, so you can continue working // with two monitors later on just like nothing has happened Radeon_VerifyMultiMode( vc, si, &mode ); // set main flags vc->independant_heads = vc->assigned_crtc[0] && si->crtc[0].chosen_displays != dd_none; if( si->num_crtc > 1 ) vc->independant_heads += vc->assigned_crtc[1] && si->crtc[1].chosen_displays != dd_none; vc->different_heads = Radeon_DifferentPorts( &mode ); SHOW_FLOW( 2, "independant heads: %d, different heads: %d", vc->independant_heads, vc->different_heads ); vc->scroll = mode.flags & B_SCROLL; SHOW_FLOW( 2, "scrolling %s", vc->scroll ? "enabled" : "disabled" ); // allocate frame buffer and cursor image memory { radeon_alloc_mem am; int format, bpp; // alloc cursor memory am.magic = RADEON_PRIVATE_DATA_MAGIC; am.size = 1024; am.memory_type = mt_local; am.global = true; if( ioctl( ai->fd, RADEON_ALLOC_MEM, &am ) == B_OK ) { vc->cursor.mem_handle = am.handle; vc->cursor.fb_offset = am.offset; } else { // too bad that we are out of mem -> set reasonable values as // it's too late to give up (ouch!) SHOW_ERROR0( 2, "no memory for cursor image!" ); vc->cursor.mem_handle = 0; vc->cursor.fb_offset = 0; } vc->cursor.data = si->local_mem + vc->cursor.fb_offset; // alloc frame buffer Radeon_GetFormat( mode.space, &format, &bpp ); vc->pitch = Radeon_RoundVWidth( mode.virtual_width, bpp ) * bpp; am.size = vc->pitch * mode.virtual_height; if( ioctl( ai->fd, RADEON_ALLOC_MEM, &am ) == B_OK ) { vc->fb_mem_handle = am.handle; vc->fb_offset = am.offset; } else { // ouch again - set reasonable values SHOW_ERROR0( 2, "no memory for frame buffer!" ); vc->fb_mem_handle = 0; vc->fb_offset = 1024; } vc->fbc.frame_buffer = si->local_mem + vc->fb_offset; vc->fbc.frame_buffer_dma = (void *)((uint8 *)si->framebuffer_pci + vc->fb_offset); vc->fbc.bytes_per_row = vc->pitch; SHOW_FLOW( 0, "frame buffer CPU-address=%x, phys-address=%x", vc->fbc.frame_buffer, vc->fbc.frame_buffer_dma ); } // multi-screen stuff Radeon_InitMultiModeVars( ai, &mode ); // GO! { routing_regs routing_values; impactv_params tv_params; status_t err1 , err2; err1 = err2 = B_OK; // we first switch off all output, so the monitor(s) won't get invalid signals if( vc->assigned_crtc[0] ) { // overwrite list of active displays to switch off displays // someone else turned on si->crtc[0].active_displays = vc->controlled_displays; Radeon_SetDPMS( ai, 0, B_DPMS_SUSPEND ); } if( vc->assigned_crtc[1] ) { si->crtc[1].active_displays = vc->controlled_displays; Radeon_SetDPMS( ai, 1, B_DPMS_SUSPEND ); } // mark crtc that will be used from now on vc->used_crtc[0] = vc->assigned_crtc[0] && si->crtc[0].chosen_displays != dd_none; vc->used_crtc[1] = vc->assigned_crtc[1] && si->crtc[1].chosen_displays != dd_none; // then change the mode if( vc->used_crtc[0] ) err1 = Radeon_SetMode( ai, &si->crtc[0], &mode, &tv_params ); if( vc->used_crtc[1] ) err2 = Radeon_SetMode( ai, &si->crtc[1], &mode, &tv_params ); SHOW_FLOW( 2, "SetModes 1=%s, 2=%s", (err1 == B_OK) ? "OK" : "FAIL", (err2 == B_OK) ? "OK" : "FAIL"); // setup signal routing Radeon_ReadMonitorRoutingRegs( ai, &routing_values ); Radeon_CalcMonitorRouting( ai, &tv_params, &routing_values ); Radeon_ProgramMonitorRouting( ai, &routing_values ); // finally, switch display(s) on if( vc->used_crtc[0] ) Radeon_SetDPMS( ai, 0, (err1 == B_OK) ? B_DPMS_ON : B_DPMS_SUSPEND ); if( vc->used_crtc[1] ) Radeon_SetDPMS( ai, 1, (err2 == B_OK) ? B_DPMS_ON : B_DPMS_SUSPEND ); OUTREGP( ai->regs, RADEON_CRTC_EXT_CNTL, 0, ~RADEON_CRTC_DISPLAY_DIS ); } SHOW_FLOW( 3, "pitch=%ld", vc->pitch ); // we'll modify bits of this reg, so save it for async access si->dac_cntl2 = INREG( ai->regs, RADEON_DAC_CNTL2 ); // setup 2D registers Radeon_Init2D( ai ); // setup position of framebuffer for 2D commands Radeon_FillStateBuffer( ai, vc->datatype ); // remember that 2D accelerator is not prepared for any virtual card si->active_vc = -1; // first move to well-defined position (to setup CRTC offset) Radeon_MoveDisplay( ai, 0, 0 ); // then to (probably faulty) user-defined pos Radeon_MoveDisplay( ai, mode.h_display_start, mode.v_display_start ); // set standard palette in direct-colour modes if( vc->used_crtc[0] ) Radeon_InitPalette( ai, 0 ); if( vc->used_crtc[1] ) Radeon_InitPalette( ai, 1 ); // initialize cursor data if( vc->used_crtc[0] ) Radeon_SetCursorColors( ai, 0 ); if( vc->used_crtc[1] ) Radeon_SetCursorColors( ai, 1 ); // sync should be settled now, so we can reenable IRQs Radeon_EnableIRQ( ai, true ); RELEASE_BEN( si->engine.lock ); // !! all this must be done after lock has been // released to avoid dead-lock !! // TBD: any invalid intermediate states? // move_cursor sets all cursor-related variables and registers vc->cursor.is_visible = false; MOVE_CURSOR( 0, 0 ); return B_OK; }