/* * Copyright 2006-2013, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * Alexander von Gluck, kallisti5@unixzen.com * Bill Randle, billr@neocat.org */ /* * It's dangerous to go alone, take this! * framebuffer -> crtc -> encoder -> transmitter -> connector -> monitor */ #include "display.h" #include #include #include "accelerant.h" #include "accelerant_protos.h" #include "bios.h" #include "connector.h" #include "displayport.h" #include "encoder.h" #define TRACE_DISPLAY #ifdef TRACE_DISPLAY extern "C" void _sPrintf(const char* format, ...); # define TRACE(x...) _sPrintf("radeon_hd: " x) #else # define TRACE(x...) ; #endif #define ERROR(x...) _sPrintf("radeon_hd: " x) /*! Populate regs with device dependant register locations */ status_t init_registers(register_info* regs, uint8 crtcID) { memset(regs, 0, sizeof(register_info)); radeon_shared_info &info = *gInfo->shared_info; if (info.chipsetID >= RADEON_CEDAR) { // Evergreen uint32 offset = 0; switch (crtcID) { case 0: offset = EVERGREEN_CRTC0_REGISTER_OFFSET; regs->vgaControl = AVIVO_D1VGA_CONTROL; break; case 1: offset = EVERGREEN_CRTC1_REGISTER_OFFSET; regs->vgaControl = AVIVO_D2VGA_CONTROL; break; case 2: offset = EVERGREEN_CRTC2_REGISTER_OFFSET; regs->vgaControl = EVERGREEN_D3VGA_CONTROL; break; case 3: offset = EVERGREEN_CRTC3_REGISTER_OFFSET; regs->vgaControl = EVERGREEN_D4VGA_CONTROL; break; case 4: offset = EVERGREEN_CRTC4_REGISTER_OFFSET; regs->vgaControl = EVERGREEN_D5VGA_CONTROL; break; case 5: offset = EVERGREEN_CRTC5_REGISTER_OFFSET; regs->vgaControl = EVERGREEN_D6VGA_CONTROL; break; default: ERROR("%s: Unknown CRTC %" B_PRIu32 "\n", __func__, crtcID); return B_ERROR; } regs->crtcOffset = offset; regs->grphEnable = EVERGREEN_GRPH_ENABLE + offset; regs->grphControl = EVERGREEN_GRPH_CONTROL + offset; regs->grphSwapControl = EVERGREEN_GRPH_SWAP_CONTROL + offset; regs->grphPrimarySurfaceAddr = EVERGREEN_GRPH_PRIMARY_SURFACE_ADDRESS + offset; regs->grphSecondarySurfaceAddr = EVERGREEN_GRPH_SECONDARY_SURFACE_ADDRESS + offset; regs->grphPrimarySurfaceAddrHigh = EVERGREEN_GRPH_PRIMARY_SURFACE_ADDRESS_HIGH + offset; regs->grphSecondarySurfaceAddrHigh = EVERGREEN_GRPH_SECONDARY_SURFACE_ADDRESS_HIGH + offset; regs->grphPitch = EVERGREEN_GRPH_PITCH + offset; regs->grphSurfaceOffsetX = EVERGREEN_GRPH_SURFACE_OFFSET_X + offset; regs->grphSurfaceOffsetY = EVERGREEN_GRPH_SURFACE_OFFSET_Y + offset; regs->grphXStart = EVERGREEN_GRPH_X_START + offset; regs->grphYStart = EVERGREEN_GRPH_Y_START + offset; regs->grphXEnd = EVERGREEN_GRPH_X_END + offset; regs->grphYEnd = EVERGREEN_GRPH_Y_END + offset; regs->modeDesktopHeight = EVERGREEN_DESKTOP_HEIGHT + offset; regs->modeDataFormat = EVERGREEN_DATA_FORMAT + offset; regs->viewportStart = EVERGREEN_VIEWPORT_START + offset; regs->viewportSize = EVERGREEN_VIEWPORT_SIZE + offset; } else if (info.chipsetID >= RADEON_RV770) { // R700 series uint32 offset = 0; switch (crtcID) { case 0: offset = R700_CRTC0_REGISTER_OFFSET; regs->vgaControl = AVIVO_D1VGA_CONTROL; regs->grphPrimarySurfaceAddrHigh = R700_D1GRPH_PRIMARY_SURFACE_ADDRESS_HIGH; break; case 1: offset = R700_CRTC1_REGISTER_OFFSET; regs->vgaControl = AVIVO_D2VGA_CONTROL; regs->grphPrimarySurfaceAddrHigh = R700_D2GRPH_PRIMARY_SURFACE_ADDRESS_HIGH; break; default: ERROR("%s: Unknown CRTC %" B_PRIu32 "\n", __func__, crtcID); return B_ERROR; } regs->crtcOffset = offset; regs->grphEnable = AVIVO_D1GRPH_ENABLE + offset; regs->grphControl = AVIVO_D1GRPH_CONTROL + offset; regs->grphSwapControl = AVIVO_D1GRPH_SWAP_CNTL + offset; regs->grphPrimarySurfaceAddr = R700_D1GRPH_PRIMARY_SURFACE_ADDRESS + offset; regs->grphSecondarySurfaceAddr = R700_D1GRPH_SECONDARY_SURFACE_ADDRESS + offset; regs->grphPitch = AVIVO_D1GRPH_PITCH + offset; regs->grphSurfaceOffsetX = AVIVO_D1GRPH_SURFACE_OFFSET_X + offset; regs->grphSurfaceOffsetY = AVIVO_D1GRPH_SURFACE_OFFSET_Y + offset; regs->grphXStart = AVIVO_D1GRPH_X_START + offset; regs->grphYStart = AVIVO_D1GRPH_Y_START + offset; regs->grphXEnd = AVIVO_D1GRPH_X_END + offset; regs->grphYEnd = AVIVO_D1GRPH_Y_END + offset; regs->modeDesktopHeight = AVIVO_D1MODE_DESKTOP_HEIGHT + offset; regs->modeDataFormat = AVIVO_D1MODE_DATA_FORMAT + offset; regs->viewportStart = AVIVO_D1MODE_VIEWPORT_START + offset; regs->viewportSize = AVIVO_D1MODE_VIEWPORT_SIZE + offset; } else if (info.chipsetID >= RADEON_RS600) { // Avivo+ uint32 offset = 0; switch (crtcID) { case 0: offset = R600_CRTC0_REGISTER_OFFSET; regs->vgaControl = AVIVO_D1VGA_CONTROL; break; case 1: offset = R600_CRTC1_REGISTER_OFFSET; regs->vgaControl = AVIVO_D2VGA_CONTROL; break; default: ERROR("%s: Unknown CRTC %" B_PRIu32 "\n", __func__, crtcID); return B_ERROR; } regs->crtcOffset = offset; regs->grphEnable = AVIVO_D1GRPH_ENABLE + offset; regs->grphControl = AVIVO_D1GRPH_CONTROL + offset; regs->grphSwapControl = AVIVO_D1GRPH_SWAP_CNTL + offset; regs->grphPrimarySurfaceAddr = AVIVO_D1GRPH_PRIMARY_SURFACE_ADDRESS + offset; regs->grphSecondarySurfaceAddr = AVIVO_D1GRPH_SECONDARY_SURFACE_ADDRESS + offset; // Surface Address high only used on r700 and higher regs->grphPrimarySurfaceAddrHigh = 0xDEAD; regs->grphSecondarySurfaceAddrHigh = 0xDEAD; regs->grphPitch = AVIVO_D1GRPH_PITCH + offset; regs->grphSurfaceOffsetX = AVIVO_D1GRPH_SURFACE_OFFSET_X + offset; regs->grphSurfaceOffsetY = AVIVO_D1GRPH_SURFACE_OFFSET_Y + offset; regs->grphXStart = AVIVO_D1GRPH_X_START + offset; regs->grphYStart = AVIVO_D1GRPH_Y_START + offset; regs->grphXEnd = AVIVO_D1GRPH_X_END + offset; regs->grphYEnd = AVIVO_D1GRPH_Y_END + offset; regs->modeDesktopHeight = AVIVO_D1MODE_DESKTOP_HEIGHT + offset; regs->modeDataFormat = AVIVO_D1MODE_DATA_FORMAT + offset; regs->viewportStart = AVIVO_D1MODE_VIEWPORT_START + offset; regs->viewportSize = AVIVO_D1MODE_VIEWPORT_SIZE + offset; } else { // this really shouldn't happen unless a driver PCIID chipset is wrong TRACE("%s, unknown Radeon chipset: %s\n", __func__, info.chipsetName); return B_ERROR; } TRACE("%s, registers for ATI chipset %s crt #%d loaded\n", __func__, info.chipsetName, crtcID); return B_OK; } status_t detect_crt_ranges(uint32 crtid) { edid1_info* edid = &gDisplay[crtid]->edidData; // Scan each display EDID description for monitor ranges for (uint32 index = 0; index < EDID1_NUM_DETAILED_MONITOR_DESC; index++) { edid1_detailed_monitor* monitor = &edid->detailed_monitor[index]; if (monitor->monitor_desc_type == EDID1_MONITOR_RANGES) { edid1_monitor_range range = monitor->data.monitor_range; gDisplay[crtid]->vfreqMin = range.min_v; /* in Hz */ gDisplay[crtid]->vfreqMax = range.max_v; gDisplay[crtid]->hfreqMin = range.min_h; /* in kHz */ gDisplay[crtid]->hfreqMax = range.max_h; return B_OK; } } return B_ERROR; } status_t detect_displays() { // reset known displays for (uint32 id = 0; id < MAX_DISPLAY; id++) { gDisplay[id]->attached = false; gDisplay[id]->powered = false; gDisplay[id]->foundRanges = false; } uint32 displayIndex = 0; for (uint32 id = 0; id < ATOM_MAX_SUPPORTED_DEVICE; id++) { if (gConnector[id]->valid == false) continue; if (displayIndex >= MAX_DISPLAY) continue; if (gConnector[id]->type == VIDEO_CONNECTOR_9DIN) { TRACE("%s: connector(%" B_PRIu32 "): Skipping 9DIN connector " "(not yet supported)\n", __func__, id); continue; } if (gConnector[id]->type == VIDEO_CONNECTOR_DP) { TRACE("%s: connector(%" B_PRIu32 "): Checking DP.\n", __func__, id); if (gConnector[id]->encoderExternal.valid == true) { // If this has a valid external encoder (dp bridge) // normally TRAVIS (LVDS) or NUTMEG (VGA) TRACE("%s: external encoder, performing bridge DDC setup\n", __func__); encoder_external_setup(id, EXTERNAL_ENCODER_ACTION_V3_DDC_SETUP); } edid1_info* edid = &gDisplay[displayIndex]->edidData; gDisplay[displayIndex]->attached = ddc2_dp_read_edid1(id, edid); // TODO: DDC Router switching for DisplayPort (and others?) if (gDisplay[displayIndex]->attached) { TRACE("%s: connector(%" B_PRIu32 "): Found DisplayPort EDID!\n", __func__, id); } } if (gConnector[id]->type == VIDEO_CONNECTOR_LVDS) { display_mode preferredMode; bool lvdsInfoFound = connector_read_mode_lvds(id, &preferredMode); TRACE("%s: connector(%" B_PRIu32 "): bit-banging LVDS for EDID.\n", __func__, id); gDisplay[displayIndex]->attached = connector_read_edid(id, &gDisplay[displayIndex]->edidData); if (!gDisplay[displayIndex]->attached && lvdsInfoFound) { // If we didn't find ddc edid data, fallback to lvdsInfo // We have to call connector_read_mode_lvds first to // collect SS data for the lvds connector TRACE("%s: connector(%" B_PRIu32 "): using AtomBIOS LVDS_Info " "preferred mode\n", __func__, id); gDisplay[displayIndex]->attached = true; memcpy(&gDisplay[displayIndex]->preferredMode, &preferredMode, sizeof(display_mode)); } } // If no display found yet, try more standard detection methods if (gDisplay[displayIndex]->attached == false) { TRACE("%s: connector(%" B_PRIu32 "): bit-banging ddc for EDID.\n", __func__, id); // Bit-bang edid from connector gDisplay[displayIndex]->attached = connector_read_edid(id, &gDisplay[displayIndex]->edidData); // Found EDID data? if (gDisplay[displayIndex]->attached) { TRACE("%s: connector(%" B_PRIu32 "): found EDID data.\n", __func__, id); if (gConnector[id]->type == VIDEO_CONNECTOR_DVII || gConnector[id]->type == VIDEO_CONNECTOR_HDMIB) { // These connectors can share gpio pins for data // communication between digital and analog encoders // (DVI-I is most common) edid1_info* edid = &gDisplay[displayIndex]->edidData; bool analogEncoder = gConnector[id]->encoder.type == VIDEO_ENCODER_TVDAC || gConnector[id]->encoder.type == VIDEO_ENCODER_DAC; bool digitalEncoder = gConnector[id]->encoder.type == VIDEO_ENCODER_TMDS; bool digitalEdid = edid->display.input_type ? true : false; if (digitalEdid && analogEncoder) { // Digital EDID + analog encoder? Lets try a load test gDisplay[displayIndex]->attached = encoder_analog_load_detect(id); } else if (!digitalEdid && digitalEncoder) { // non-digital EDID + digital encoder? Nope. gDisplay[displayIndex]->attached = false; } // Else... everything aligns as it should and attached = 1 } } } if (gDisplay[displayIndex]->attached != true) { // Nothing interesting here, move along continue; } // We found a valid / attached display gDisplay[displayIndex]->connectorIndex = id; // Populate physical connector index from gConnector init_registers(gDisplay[displayIndex]->regs, displayIndex); if (gDisplay[displayIndex]->preferredMode.virtual_width > 0) { // Found a single preferred mode gDisplay[displayIndex]->foundRanges = false; } else { // Use edid data and pull ranges if (detect_crt_ranges(displayIndex) == B_OK) gDisplay[displayIndex]->foundRanges = true; } displayIndex++; } // fallback if no attached monitors were found if (displayIndex == 0) { // This is a hack, however as we don't support HPD just yet, // it tries to prevent a "no displays" situation. ERROR("%s: ERROR: 0 attached monitors were found on display connectors." " Injecting first connector as a last resort.\n", __func__); for (uint32 id = 0; id < ATOM_MAX_SUPPORTED_DEVICE; id++) { // skip TV DAC connectors as likely fallback isn't for TV if (gConnector[id]->encoder.type == VIDEO_ENCODER_TVDAC) continue; gDisplay[0]->attached = true; gDisplay[0]->connectorIndex = id; init_registers(gDisplay[0]->regs, 0); if (detect_crt_ranges(0) == B_OK) gDisplay[0]->foundRanges = true; break; } } // Initial boot state is the first two crtc's powered if (gDisplay[0]->attached == true) gDisplay[0]->powered = true; if (gDisplay[1]->attached == true) gDisplay[1]->powered = true; return B_OK; } void debug_displays() { TRACE("Currently detected monitors===============\n"); for (uint32 id = 0; id < MAX_DISPLAY; id++) { ERROR("Display #%" B_PRIu32 " attached = %s\n", id, gDisplay[id]->attached ? "true" : "false"); uint32 connectorIndex = gDisplay[id]->connectorIndex; if (gDisplay[id]->attached) { uint32 connectorType = gConnector[connectorIndex]->type; uint32 encoderType = gConnector[connectorIndex]->encoder.type; ERROR(" + connector ID: %" B_PRIu32 "\n", connectorIndex); ERROR(" + connector type: %s\n", get_connector_name(connectorType)); ERROR(" + encoder type: %s\n", get_encoder_name(encoderType)); ERROR(" + limits: Vert Min/Max: %" B_PRIu32 "/%" B_PRIu32"\n", gDisplay[id]->vfreqMin, gDisplay[id]->vfreqMax); ERROR(" + limits: Horz Min/Max: %" B_PRIu32 "/%" B_PRIu32"\n", gDisplay[id]->hfreqMin, gDisplay[id]->hfreqMax); } } TRACE("==========================================\n"); } uint32 display_get_encoder_mode(uint32 connectorIndex) { // Is external DisplayPort Bridge? if (gConnector[connectorIndex]->encoderExternal.valid == true && gConnector[connectorIndex]->encoderExternal.isDPBridge == true) { return ATOM_ENCODER_MODE_DP; } // DVO Encoders (should be bridges) switch (gConnector[connectorIndex]->encoder.objectID) { case ENCODER_OBJECT_ID_INTERNAL_DVO1: case ENCODER_OBJECT_ID_INTERNAL_DDI: case ENCODER_OBJECT_ID_INTERNAL_KLDSCP_DVO1: return ATOM_ENCODER_MODE_DVO; } // Find display for connector so we can identify source of edid data int32 crtcID = -1; for (int32 id = 0; id < MAX_DISPLAY; id++) { if (gDisplay[id]->connectorIndex == connectorIndex) { crtcID = id; break; } } bool edidDigital = false; if (crtcID == -1) { ERROR("%s: BUG: executed on connector without assigned display!\n", __func__); } else { edid1_info* edid = &gDisplay[crtcID]->edidData; edidDigital = edid->display.input_type ? true : false; } // Normal encoder situations switch (gConnector[connectorIndex]->type) { case VIDEO_CONNECTOR_DVII: case VIDEO_CONNECTOR_HDMIB: /* HDMI-B is DL-DVI; analog works fine */ // TODO: if audio detected on edid and DCE4, ATOM_ENCODER_MODE_DVI // if audio detected on edid not DCE4, ATOM_ENCODER_MODE_HDMI if (edidDigital) return ATOM_ENCODER_MODE_DVI; else return ATOM_ENCODER_MODE_CRT; break; case VIDEO_CONNECTOR_DVID: case VIDEO_CONNECTOR_HDMIA: default: // TODO: if audio detected on edid and DCE4, ATOM_ENCODER_MODE_DVI // if audio detected on edid not DCE4, ATOM_ENCODER_MODE_HDMI return ATOM_ENCODER_MODE_DVI; case VIDEO_CONNECTOR_LVDS: return ATOM_ENCODER_MODE_LVDS; case VIDEO_CONNECTOR_DP: // dig_connector = radeon_connector->con_priv; // if ((dig_connector->dp_sink_type // == CONNECTOR_OBJECT_ID_DISPLAYPORT) // || (dig_connector->dp_sink_type == CONNECTOR_OBJECT_ID_eDP)) { // return ATOM_ENCODER_MODE_DP; // } // TODO: if audio detected on edid and DCE4, ATOM_ENCODER_MODE_DVI // if audio detected on edid not DCE4, ATOM_ENCODER_MODE_HDMI return ATOM_ENCODER_MODE_DP; case VIDEO_CONNECTOR_EDP: return ATOM_ENCODER_MODE_DP; case VIDEO_CONNECTOR_DVIA: case VIDEO_CONNECTOR_VGA: return ATOM_ENCODER_MODE_CRT; case VIDEO_CONNECTOR_COMPOSITE: case VIDEO_CONNECTOR_SVIDEO: case VIDEO_CONNECTOR_9DIN: return ATOM_ENCODER_MODE_TV; } } void display_crtc_lock(uint8 crtcID, int command) { TRACE("%s\n", __func__); ENABLE_CRTC_PS_ALLOCATION args; int index = GetIndexIntoMasterTable(COMMAND, UpdateCRTC_DoubleBufferRegisters); memset(&args, 0, sizeof(args)); args.ucCRTC = crtcID; args.ucEnable = command; atom_execute_table(gAtomContext, index, (uint32*)&args); } void display_crtc_blank(uint8 crtcID, int command) { TRACE("%s\n", __func__); BLANK_CRTC_PS_ALLOCATION args; int index = GetIndexIntoMasterTable(COMMAND, BlankCRTC); memset(&args, 0, sizeof(args)); args.ucCRTC = crtcID; args.ucBlanking = command; args.usBlackColorRCr = 0; args.usBlackColorGY = 0; args.usBlackColorBCb = 0; atom_execute_table(gAtomContext, index, (uint32*)&args); } void display_crtc_scale(uint8 crtcID, display_mode* mode) { TRACE("%s\n", __func__); ENABLE_SCALER_PS_ALLOCATION args; int index = GetIndexIntoMasterTable(COMMAND, EnableScaler); memset(&args, 0, sizeof(args)); args.ucScaler = crtcID; args.ucEnable = ATOM_SCALER_DISABLE; atom_execute_table(gAtomContext, index, (uint32*)&args); } void display_crtc_dpms(uint8 crtcID, int mode) { radeon_shared_info &info = *gInfo->shared_info; switch (mode) { case B_DPMS_ON: TRACE("%s: crtc %" B_PRIu8 " dpms powerup\n", __func__, crtcID); if (gDisplay[crtcID]->attached == false) return; display_crtc_power(crtcID, ATOM_ENABLE); gDisplay[crtcID]->powered = true; if (info.dceMajor >= 3 && info.dceMajor < 6) display_crtc_memreq(crtcID, ATOM_ENABLE); display_crtc_blank(crtcID, ATOM_BLANKING_OFF); break; case B_DPMS_STAND_BY: case B_DPMS_SUSPEND: case B_DPMS_OFF: TRACE("%s: crtc %" B_PRIu8 " dpms powerdown\n", __func__, crtcID); if (gDisplay[crtcID]->attached == false) return; if (gDisplay[crtcID]->powered == true) display_crtc_blank(crtcID, ATOM_BLANKING); if (info.dceMajor >= 3 && info.dceMajor < 6) display_crtc_memreq(crtcID, ATOM_DISABLE); display_crtc_power(crtcID, ATOM_DISABLE); gDisplay[crtcID]->powered = false; } } void display_dce45_crtc_load_lut(uint8 crtcID) { radeon_shared_info &info = *gInfo->shared_info; register_info* regs = gDisplay[crtcID]->regs; TRACE("%s: crtcID %" B_PRIu8 "\n", __func__, crtcID); uint16* r = info.color_data; uint16* g = r + 256; uint16* b = r + 512; if (info.dceMajor >= 5) { Write32(OUT, NI_INPUT_CSC_CONTROL + regs->crtcOffset, NI_INPUT_CSC_GRPH_MODE(NI_INPUT_CSC_BYPASS) | NI_INPUT_CSC_OVL_MODE(NI_INPUT_CSC_BYPASS)); Write32(OUT, NI_PRESCALE_GRPH_CONTROL + regs->crtcOffset, NI_GRPH_PRESCALE_BYPASS); Write32(OUT, NI_PRESCALE_OVL_CONTROL + regs->crtcOffset, NI_OVL_PRESCALE_BYPASS); Write32(OUT, NI_INPUT_GAMMA_CONTROL + regs->crtcOffset, NI_GRPH_INPUT_GAMMA_MODE(NI_INPUT_GAMMA_USE_LUT) | NI_OVL_INPUT_GAMMA_MODE(NI_INPUT_GAMMA_USE_LUT)); } Write32(OUT, EVERGREEN_DC_LUT_CONTROL + regs->crtcOffset, 0); Write32(OUT, EVERGREEN_DC_LUT_BLACK_OFFSET_BLUE + regs->crtcOffset, 0); Write32(OUT, EVERGREEN_DC_LUT_BLACK_OFFSET_GREEN + regs->crtcOffset, 0); Write32(OUT, EVERGREEN_DC_LUT_BLACK_OFFSET_RED + regs->crtcOffset, 0); Write32(OUT, EVERGREEN_DC_LUT_WHITE_OFFSET_BLUE + regs->crtcOffset, 0xffff); Write32(OUT, EVERGREEN_DC_LUT_WHITE_OFFSET_GREEN + regs->crtcOffset, 0xffff); Write32(OUT, EVERGREEN_DC_LUT_WHITE_OFFSET_RED + regs->crtcOffset, 0xffff); Write32(OUT, EVERGREEN_DC_LUT_RW_MODE, 0); Write32(OUT, EVERGREEN_DC_LUT_WRITE_EN_MASK, 0x00000007); Write32(OUT, EVERGREEN_DC_LUT_RW_INDEX, 0); for (int i = 0; i < 256; i++) { Write32(OUT, EVERGREEN_DC_LUT_30_COLOR + regs->crtcOffset, (r[i] << 20) | (g[i] << 10) | (b[i] << 0)); } if (info.dceMajor >= 5) { Write32(OUT, NI_DEGAMMA_CONTROL + regs->crtcOffset, (NI_GRPH_DEGAMMA_MODE(NI_DEGAMMA_BYPASS) | NI_OVL_DEGAMMA_MODE(NI_DEGAMMA_BYPASS) | NI_ICON_DEGAMMA_MODE(NI_DEGAMMA_BYPASS) | NI_CURSOR_DEGAMMA_MODE(NI_DEGAMMA_BYPASS))); Write32(OUT, NI_GAMUT_REMAP_CONTROL + regs->crtcOffset, (NI_GRPH_GAMUT_REMAP_MODE(NI_GAMUT_REMAP_BYPASS) | NI_OVL_GAMUT_REMAP_MODE(NI_GAMUT_REMAP_BYPASS))); Write32(OUT, NI_REGAMMA_CONTROL + regs->crtcOffset, (NI_GRPH_REGAMMA_MODE(NI_REGAMMA_BYPASS) | NI_OVL_REGAMMA_MODE(NI_REGAMMA_BYPASS))); Write32(OUT, NI_OUTPUT_CSC_CONTROL + regs->crtcOffset, (NI_OUTPUT_CSC_GRPH_MODE(NI_OUTPUT_CSC_BYPASS) | NI_OUTPUT_CSC_OVL_MODE(NI_OUTPUT_CSC_BYPASS))); /* XXX match this to the depth of the crtc fmt block, move to modeset? */ Write32(OUT, 0x6940 + regs->crtcOffset, 0); } } void display_avivo_crtc_load_lut(uint8 crtcID) { radeon_shared_info &info = *gInfo->shared_info; register_info* regs = gDisplay[crtcID]->regs; TRACE("%s: crtcID %" B_PRIu8 "\n", __func__, crtcID); uint16* r = info.color_data; uint16* g = r + 256; uint16* b = r + 512; Write32(OUT, AVIVO_DC_LUTA_CONTROL + regs->crtcOffset, 0); Write32(OUT, AVIVO_DC_LUTA_BLACK_OFFSET_BLUE + regs->crtcOffset, 0); Write32(OUT, AVIVO_DC_LUTA_BLACK_OFFSET_GREEN + regs->crtcOffset, 0); Write32(OUT, AVIVO_DC_LUTA_BLACK_OFFSET_RED + regs->crtcOffset, 0); Write32(OUT, AVIVO_DC_LUTA_WHITE_OFFSET_BLUE + regs->crtcOffset, 0xffff); Write32(OUT, AVIVO_DC_LUTA_WHITE_OFFSET_GREEN + regs->crtcOffset, 0xffff); Write32(OUT, AVIVO_DC_LUTA_WHITE_OFFSET_RED + regs->crtcOffset, 0xffff); Write32(OUT, AVIVO_DC_LUT_RW_SELECT, crtcID); Write32(OUT, AVIVO_DC_LUT_RW_MODE, 0); Write32(OUT, AVIVO_DC_LUT_WRITE_EN_MASK, 0x0000003f); Write32(OUT, AVIVO_DC_LUT_RW_INDEX, 0); for (int i = 0; i < 256; i++) { Write32(OUT, AVIVO_DC_LUT_30_COLOR, (r[i] << 20) | (g[i] << 10) | (b[i] << 0)); } Write32(OUT, AVIVO_D1GRPH_LUT_SEL + regs->crtcOffset, crtcID); } void display_crtc_fb_set(uint8 crtcID, display_mode* mode) { radeon_shared_info &info = *gInfo->shared_info; register_info* regs = gDisplay[crtcID]->regs; uint16* r = info.color_data; uint16* g = r + 256; uint16* b = r + 512; uint32 fbSwap; if (info.dceMajor >= 4) fbSwap = EVERGREEN_GRPH_ENDIAN_SWAP(EVERGREEN_GRPH_ENDIAN_NONE); else fbSwap = R600_D1GRPH_SWAP_ENDIAN_NONE; uint32 fbFormat; uint32 bytesPerPixel; uint32 bitsPerPixel; switch (mode->space) { case B_CMAP8: bytesPerPixel = 1; bitsPerPixel = 8; if (info.dceMajor >= 4) { fbFormat = (EVERGREEN_GRPH_DEPTH(EVERGREEN_GRPH_DEPTH_8BPP) | EVERGREEN_GRPH_FORMAT(EVERGREEN_GRPH_FORMAT_INDEXED)); } else { fbFormat = AVIVO_D1GRPH_CONTROL_DEPTH_8BPP | AVIVO_D1GRPH_CONTROL_8BPP_INDEXED; } // TODO: copy system color map into shared info break; case B_RGB15_LITTLE: bytesPerPixel = 2; bitsPerPixel = 15; if (info.dceMajor >= 4) { fbFormat = (EVERGREEN_GRPH_DEPTH(EVERGREEN_GRPH_DEPTH_16BPP) | EVERGREEN_GRPH_FORMAT(EVERGREEN_GRPH_FORMAT_ARGB1555)); } else { fbFormat = AVIVO_D1GRPH_CONTROL_DEPTH_16BPP | AVIVO_D1GRPH_CONTROL_16BPP_ARGB1555; } break; case B_RGB16_LITTLE: bytesPerPixel = 2; bitsPerPixel = 16; if (info.dceMajor >= 4) { fbFormat = (EVERGREEN_GRPH_DEPTH(EVERGREEN_GRPH_DEPTH_16BPP) | EVERGREEN_GRPH_FORMAT(EVERGREEN_GRPH_FORMAT_ARGB565)); #ifdef __POWERPC__ fbSwap = EVERGREEN_GRPH_ENDIAN_SWAP(EVERGREEN_GRPH_ENDIAN_8IN16); #endif } else { fbFormat = AVIVO_D1GRPH_CONTROL_DEPTH_16BPP | AVIVO_D1GRPH_CONTROL_16BPP_RGB565; #ifdef __POWERPC__ fbSwap = R600_D1GRPH_SWAP_ENDIAN_16BIT; #endif } { // default gamma table uint16 gamma = 0; for (int i = 0; i < 256; i++) { r[i] = gamma; g[i] = gamma; b[i] = gamma; gamma += 4; } } break; case B_RGB24_LITTLE: case B_RGB32_LITTLE: default: bytesPerPixel = 4; bitsPerPixel = 32; if (info.dceMajor >= 4) { fbFormat = (EVERGREEN_GRPH_DEPTH(EVERGREEN_GRPH_DEPTH_32BPP) | EVERGREEN_GRPH_FORMAT(EVERGREEN_GRPH_FORMAT_ARGB8888)); #ifdef __POWERPC__ fbSwap = EVERGREEN_GRPH_ENDIAN_SWAP(EVERGREEN_GRPH_ENDIAN_8IN32); #endif } else { fbFormat = AVIVO_D1GRPH_CONTROL_DEPTH_32BPP | AVIVO_D1GRPH_CONTROL_32BPP_ARGB8888; #ifdef __POWERPC__ fbSwap = R600_D1GRPH_SWAP_ENDIAN_32BIT; #endif } { // default gamma table uint16 gamma = 0; for (int i = 0; i < 256; i++) { r[i] = gamma; g[i] = gamma; b[i] = gamma; gamma += 4; } } break; } Write32(OUT, regs->vgaControl, 0); uint64 fbAddress = gInfo->fb.vramStart; TRACE("%s: Framebuffer at: 0x%" B_PRIX64 "\n", __func__, fbAddress); if (info.chipsetID >= RADEON_RV770) { TRACE("%s: Set SurfaceAddress High: 0x%" B_PRIX32 "\n", __func__, (fbAddress >> 32) & 0xf); Write32(OUT, regs->grphPrimarySurfaceAddrHigh, (fbAddress >> 32) & 0xf); Write32(OUT, regs->grphSecondarySurfaceAddrHigh, (fbAddress >> 32) & 0xf); } TRACE("%s: Set SurfaceAddress: 0x%" B_PRIX64 "\n", __func__, (fbAddress & 0xFFFFFFFF)); Write32(OUT, regs->grphPrimarySurfaceAddr, (fbAddress & 0xFFFFFFFF)); Write32(OUT, regs->grphSecondarySurfaceAddr, (fbAddress & 0xFFFFFFFF)); if (info.chipsetID >= RADEON_R600) { Write32(CRT, regs->grphControl, fbFormat); Write32(CRT, regs->grphSwapControl, fbSwap); } // TODO: Technically if chip >= RS600 int largeAlign = (info.dceMajor >= 2) ? 1 : 0; // Align our framebuffer width uint32 widthAligned = mode->virtual_width; uint32 pitchMask = 0; switch (bytesPerPixel) { case 1: pitchMask = largeAlign ? 255 : 127; break; case 2: pitchMask = largeAlign ? 127 : 31; break; case 3: case 4: pitchMask = largeAlign ? 63 : 15; break; } widthAligned += pitchMask; widthAligned &= ~pitchMask; TRACE("%s: fb: %" B_PRIu32 "x%" B_PRIu32 " (%" B_PRIu32 " bpp)\n", __func__, mode->timing.h_display, mode->timing.v_display, bitsPerPixel); TRACE("%s: fb pitch: %" B_PRIu32 " \n", __func__, widthAligned); Write32(CRT, regs->grphSurfaceOffsetX, 0); Write32(CRT, regs->grphSurfaceOffsetY, 0); Write32(CRT, regs->grphXStart, 0); Write32(CRT, regs->grphYStart, 0); Write32(CRT, regs->grphXEnd, mode->virtual_width); Write32(CRT, regs->grphYEnd, mode->virtual_height); Write32(CRT, regs->grphPitch, widthAligned); Write32(CRT, regs->grphEnable, 1); // Enable Frame buffer Write32(CRT, regs->modeDesktopHeight, mode->virtual_height); uint32 viewportWidth = mode->timing.h_display; uint32 viewportHeight = (mode->timing.v_display + 1) & ~1; Write32(CRT, regs->viewportStart, 0); Write32(CRT, regs->viewportSize, (viewportWidth << 16) | viewportHeight); // Pageflip setup if (info.dceMajor >= 4) { uint32 tmp = Read32(OUT, EVERGREEN_GRPH_FLIP_CONTROL + regs->crtcOffset); tmp &= ~EVERGREEN_GRPH_SURFACE_UPDATE_H_RETRACE_EN; Write32(OUT, EVERGREEN_GRPH_FLIP_CONTROL + regs->crtcOffset, tmp); Write32(OUT, EVERGREEN_MASTER_UPDATE_MODE + regs->crtcOffset, 0); // Pageflip to happen anywhere in vblank display_dce45_crtc_load_lut(crtcID); } else { uint32 tmp = Read32(OUT, AVIVO_D1GRPH_FLIP_CONTROL + regs->crtcOffset); tmp &= ~AVIVO_D1GRPH_SURFACE_UPDATE_H_RETRACE_EN; Write32(OUT, AVIVO_D1GRPH_FLIP_CONTROL + regs->crtcOffset, tmp); Write32(OUT, AVIVO_D1MODE_MASTER_UPDATE_MODE + regs->crtcOffset, 0); // Pageflip to happen anywhere in vblank display_avivo_crtc_load_lut(crtcID); } // update shared info gInfo->shared_info->bytes_per_row = widthAligned * bytesPerPixel; gInfo->shared_info->current_mode = *mode; gInfo->shared_info->bits_per_pixel = bitsPerPixel; } void display_crtc_set(uint8 crtcID, display_mode* mode) { display_timing& displayTiming = mode->timing; TRACE("%s called to do %dx%d\n", __func__, displayTiming.h_display, displayTiming.v_display); SET_CRTC_TIMING_PARAMETERS_PS_ALLOCATION args; int index = GetIndexIntoMasterTable(COMMAND, SetCRTC_Timing); uint16 misc = 0; memset(&args, 0, sizeof(args)); args.usH_Total = B_HOST_TO_LENDIAN_INT16(displayTiming.h_total); args.usH_Disp = B_HOST_TO_LENDIAN_INT16(displayTiming.h_display); args.usH_SyncStart = B_HOST_TO_LENDIAN_INT16(displayTiming.h_sync_start); args.usH_SyncWidth = B_HOST_TO_LENDIAN_INT16(displayTiming.h_sync_end - displayTiming.h_sync_start); args.usV_Total = B_HOST_TO_LENDIAN_INT16(displayTiming.v_total); args.usV_Disp = B_HOST_TO_LENDIAN_INT16(displayTiming.v_display); args.usV_SyncStart = B_HOST_TO_LENDIAN_INT16(displayTiming.v_sync_start); args.usV_SyncWidth = B_HOST_TO_LENDIAN_INT16(displayTiming.v_sync_end - displayTiming.v_sync_start); args.ucOverscanRight = 0; args.ucOverscanLeft = 0; args.ucOverscanBottom = 0; args.ucOverscanTop = 0; if ((displayTiming.flags & B_POSITIVE_HSYNC) == 0) misc |= ATOM_HSYNC_POLARITY; if ((displayTiming.flags & B_POSITIVE_VSYNC) == 0) misc |= ATOM_VSYNC_POLARITY; args.susModeMiscInfo.usAccess = B_HOST_TO_LENDIAN_INT16(misc); args.ucCRTC = crtcID; atom_execute_table(gAtomContext, index, (uint32*)&args); } void display_crtc_set_dtd(uint8 crtcID, display_mode* mode) { display_timing& displayTiming = mode->timing; TRACE("%s called to do %dx%d\n", __func__, displayTiming.h_display, displayTiming.v_display); SET_CRTC_USING_DTD_TIMING_PARAMETERS args; int index = GetIndexIntoMasterTable(COMMAND, SetCRTC_UsingDTDTiming); uint16 misc = 0; memset(&args, 0, sizeof(args)); // Note: the code below assumes H & V borders are both zero uint16 blankStart = MIN(displayTiming.h_sync_start, displayTiming.h_display); uint16 blankEnd = MAX(displayTiming.h_sync_end, displayTiming.h_total); args.usH_Size = B_HOST_TO_LENDIAN_INT16(displayTiming.h_display); args.usH_Blanking_Time = B_HOST_TO_LENDIAN_INT16(blankEnd - blankStart); blankStart = MIN(displayTiming.v_sync_start, displayTiming.v_display); blankEnd = MAX(displayTiming.v_sync_end, displayTiming.v_total); args.usV_Size = B_HOST_TO_LENDIAN_INT16(displayTiming.v_display); args.usV_Blanking_Time = B_HOST_TO_LENDIAN_INT16(blankEnd - blankStart); args.usH_SyncOffset = B_HOST_TO_LENDIAN_INT16(displayTiming.h_sync_start - displayTiming.h_display); args.usH_SyncWidth = B_HOST_TO_LENDIAN_INT16(displayTiming.h_sync_end - displayTiming.h_sync_start); args.usV_SyncOffset = B_HOST_TO_LENDIAN_INT16(displayTiming.v_sync_start - displayTiming.v_display); args.usV_SyncWidth = B_HOST_TO_LENDIAN_INT16(displayTiming.v_sync_end - displayTiming.v_sync_start); args.ucH_Border = 0; args.ucV_Border = 0; if ((displayTiming.flags & B_POSITIVE_HSYNC) == 0) misc |= ATOM_HSYNC_POLARITY; if ((displayTiming.flags & B_POSITIVE_VSYNC) == 0) misc |= ATOM_VSYNC_POLARITY; args.susModeMiscInfo.usAccess = B_HOST_TO_LENDIAN_INT16(misc); args.ucCRTC = crtcID; atom_execute_table(gAtomContext, index, (uint32*)&args); } void display_crtc_ss(pll_info* pll, int command) { TRACE("%s\n", __func__); radeon_shared_info &info = *gInfo->shared_info; if (command == ATOM_ENABLE) { if (pll->ssPercentage == 0) { TRACE("%s: ssPercentage 0, ignoring SS request\n", __func__); return; } if ((pll->ssType & ATOM_EXTERNAL_SS_MASK) != 0) { TRACE("%s: external SS, ignoring SS request\n", __func__); return; } } else { if (pll_usage_count(pll->id) > 1) { // TODO: Check if PLL has SS enabled on any other displays, if so // we need to also skip this function. TRACE("%s: TODO: shared PLL detected!\n", __func__); } } int index = GetIndexIntoMasterTable(COMMAND, EnableSpreadSpectrumOnPPLL); union enableSS { ENABLE_LVDS_SS_PARAMETERS lvds_ss; ENABLE_LVDS_SS_PARAMETERS_V2 lvds_ss_2; ENABLE_SPREAD_SPECTRUM_ON_PPLL_PS_ALLOCATION v1; ENABLE_SPREAD_SPECTRUM_ON_PPLL_V2 v2; ENABLE_SPREAD_SPECTRUM_ON_PPLL_V3 v3; }; union enableSS args; memset(&args, 0, sizeof(args)); if (info.dceMajor >= 5) { args.v3.usSpreadSpectrumAmountFrac = B_HOST_TO_LENDIAN_INT16(0); args.v3.ucSpreadSpectrumType = pll->ssType & ATOM_SS_CENTRE_SPREAD_MODE_MASK; switch (pll->id) { case ATOM_PPLL1: args.v3.ucSpreadSpectrumType |= ATOM_PPLL_SS_TYPE_V3_P1PLL; break; case ATOM_PPLL2: args.v3.ucSpreadSpectrumType |= ATOM_PPLL_SS_TYPE_V3_P2PLL; break; case ATOM_DCPLL: args.v3.ucSpreadSpectrumType |= ATOM_PPLL_SS_TYPE_V3_DCPLL; break; case ATOM_PPLL_INVALID: return; default: ERROR("%s: BUG: Invalid PLL ID!\n", __func__); return; } args.v3.usSpreadSpectrumAmount = B_HOST_TO_LENDIAN_INT16(pll->ssAmount); args.v3.usSpreadSpectrumStep = B_HOST_TO_LENDIAN_INT16(pll->ssStep); args.v3.ucEnable = command; } else if (info.dceMajor >= 4) { args.v2.usSpreadSpectrumPercentage = B_HOST_TO_LENDIAN_INT16(pll->ssPercentage); args.v2.ucSpreadSpectrumType = pll->ssType & ATOM_SS_CENTRE_SPREAD_MODE_MASK; switch (pll->id) { case ATOM_PPLL1: args.v2.ucSpreadSpectrumType |= ATOM_PPLL_SS_TYPE_V2_P1PLL; break; case ATOM_PPLL2: args.v2.ucSpreadSpectrumType |= ATOM_PPLL_SS_TYPE_V3_P2PLL; break; case ATOM_DCPLL: args.v2.ucSpreadSpectrumType |= ATOM_PPLL_SS_TYPE_V3_DCPLL; break; case ATOM_PPLL_INVALID: return; default: ERROR("%s: BUG: Invalid PLL ID!\n", __func__); return; } args.v2.usSpreadSpectrumAmount = B_HOST_TO_LENDIAN_INT16(pll->ssAmount); args.v2.usSpreadSpectrumStep = B_HOST_TO_LENDIAN_INT16(pll->ssStep); args.v2.ucEnable = command; } else if (info.dceMajor >= 3) { args.v1.usSpreadSpectrumPercentage = B_HOST_TO_LENDIAN_INT16(pll->ssPercentage); args.v1.ucSpreadSpectrumType = pll->ssType & ATOM_SS_CENTRE_SPREAD_MODE_MASK; args.v1.ucSpreadSpectrumStep = pll->ssStep; args.v1.ucSpreadSpectrumDelay = pll->ssDelay; args.v1.ucSpreadSpectrumRange = pll->ssRange; args.v1.ucPpll = pll->id; args.v1.ucEnable = command; } else if (info.dceMajor >= 2) { if (command == ATOM_DISABLE) { radeon_gpu_ss_control(pll, false); return; } args.lvds_ss_2.usSpreadSpectrumPercentage = B_HOST_TO_LENDIAN_INT16(pll->ssPercentage); args.lvds_ss_2.ucSpreadSpectrumType = pll->ssType & ATOM_SS_CENTRE_SPREAD_MODE_MASK; args.lvds_ss_2.ucSpreadSpectrumStep = pll->ssStep; args.lvds_ss_2.ucSpreadSpectrumDelay = pll->ssDelay; args.lvds_ss_2.ucSpreadSpectrumRange = pll->ssRange; args.lvds_ss_2.ucEnable = command; } else { ERROR("%s: TODO: Old card SS control\n", __func__); return; } atom_execute_table(gAtomContext, index, (uint32*)&args); } void display_crtc_power(uint8 crtcID, int command) { TRACE("%s\n", __func__); int index = GetIndexIntoMasterTable(COMMAND, EnableCRTC); ENABLE_CRTC_PS_ALLOCATION args; memset(&args, 0, sizeof(args)); args.ucCRTC = crtcID; args.ucEnable = command; atom_execute_table(gAtomContext, index, (uint32*)&args); } void display_crtc_memreq(uint8 crtcID, int command) { TRACE("%s\n", __func__); int index = GetIndexIntoMasterTable(COMMAND, EnableCRTCMemReq); ENABLE_CRTC_PS_ALLOCATION args; memset(&args, 0, sizeof(args)); args.ucCRTC = crtcID; args.ucEnable = command; atom_execute_table(gAtomContext, index, (uint32*)&args); }