⛏️ index : haiku.git

/*
 * Copyright 2006-2013, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Support for i915 chipset and up based on the X driver,
 * Copyright 2006-2007 Intel Corporation.
 *
 * Authors:
 *		Axel Dörfler, axeld@pinc-software.de
 *		Alexander von Gluck, kallisti5@unixzen.com
 */


#include "mode.h"

#include <create_display_modes.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "accelerant.h"
#include "accelerant_protos.h"
#include "bios.h"
#include "connector.h"
#include "display.h"
#include "displayport.h"
#include "encoder.h"
#include "pll.h"
#include "utility.h"


#define TRACE_MODE
#ifdef TRACE_MODE
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)


status_t
create_mode_list(void)
{
	// TODO: multi-monitor?  for now we use VESA and not gDisplay edid
	uint8 crtcID = 0;

	const color_space kRadeonHDSpaces[] = {B_RGB32_LITTLE, B_RGB24_LITTLE,
		B_RGB16_LITTLE, B_RGB15_LITTLE, B_CMAP8};

	gInfo->mode_list_area = create_display_modes("radeon HD modes",
		&gDisplay[crtcID]->edidData, NULL, 0, kRadeonHDSpaces,
		B_COUNT_OF(kRadeonHDSpaces), is_mode_supported, &gInfo->mode_list,
		&gInfo->shared_info->mode_count);
	if (gInfo->mode_list_area < B_OK)
		return gInfo->mode_list_area;

	gInfo->shared_info->mode_list_area = gInfo->mode_list_area;

	return B_OK;
}


//	#pragma mark -


uint32
radeon_accelerant_mode_count(void)
{
	TRACE("%s\n", __func__);
	// TODO: multi-monitor?  we need crtcid here

	return gInfo->shared_info->mode_count;
}


status_t
radeon_get_mode_list(display_mode* modeList)
{
	TRACE("%s\n", __func__);
	// TODO: multi-monitor?  we need crtcid here
	memcpy(modeList, gInfo->mode_list,
		gInfo->shared_info->mode_count * sizeof(display_mode));
	return B_OK;
}


status_t
radeon_get_preferred_mode(display_mode* preferredMode)
{
	TRACE("%s\n", __func__);
	// TODO: multi-monitor?  we need crtcid here

	uint8_t crtc = 0;

	if (gDisplay[crtc]->preferredMode.virtual_width > 0
		&& gDisplay[crtc]->preferredMode.virtual_height > 0) {
		TRACE("%s: preferred mode was found for display %" B_PRIu8 "\n",
			__func__, crtc);
		memcpy(preferredMode, &gDisplay[crtc]->preferredMode,
			sizeof(gDisplay[crtc]->preferredMode));
		return B_OK;
	}

	return B_ERROR;
}


status_t
radeon_get_edid_info(void* info, size_t size, uint32* edid_version)
{
	// TODO: multi-monitor?  for now we use display 0
	uint8 crtcID = 0;

	TRACE("%s\n", __func__);
	if (!gInfo->shared_info->has_edid)
		return B_ERROR;
	if (size < sizeof(struct edid1_info))
		return B_BUFFER_OVERFLOW;

	//memcpy(info, &gInfo->shared_info->edid_info, sizeof(struct edid1_info));
		// VESA
	memcpy(info, &gDisplay[crtcID]->edidData, sizeof(struct edid1_info));
		// Display 0

	*edid_version = EDID_VERSION_1;

	return B_OK;
}


uint32
radeon_dpms_capabilities(void)
{
	// These should be pretty universally supported on Radeon HD cards
	return B_DPMS_ON | B_DPMS_STAND_BY | B_DPMS_SUSPEND | B_DPMS_OFF;
}


uint32
radeon_dpms_mode(void)
{
	// TODO: this really isn't a good long-term solution
	// we may need to look at the encoder dpms scratch registers
	return gInfo->dpms_mode;
}


void
radeon_dpms_set(uint8 id, int mode)
{
	if (mode == B_DPMS_ON) {
		display_crtc_dpms(id, mode);
		encoder_dpms_set(id, mode);
	} else {
		encoder_dpms_set(id, mode);
		display_crtc_dpms(id, mode);
	}
	gInfo->dpms_mode = mode;
}


void
radeon_dpms_set_hook(int mode)
{
	// TODO: multi-monitor? 

	uint8 crtcID = 0;

	if (gDisplay[crtcID]->attached)
		radeon_dpms_set(crtcID, mode);
}


status_t
radeon_set_display_mode(display_mode* mode)
{
	// TODO: multi-monitor? For now we set the mode on
	// the first display found.

	TRACE("%s\n", __func__);
	TRACE("  mode->space: %#" B_PRIx32 "\n", mode->space);
	TRACE("  mode->virtual_width: %" B_PRIu16 "\n", mode->virtual_width);
	TRACE("  mode->virtual_height: %" B_PRIu16 "\n", mode->virtual_height);
	TRACE("  mode->h_display_start: %" B_PRIu16 "\n", mode->h_display_start);
	TRACE("  mode->v_display_start: %" B_PRIu16 "\n", mode->v_display_start);
	TRACE("  mode->flags: %#" B_PRIx32 "\n", mode->flags);

	uint8 crtcID = 0;

	if (gDisplay[crtcID]->attached == false)
		return B_ERROR;

	// Copy this display mode into the "current mode" for the display
	memcpy(&gDisplay[crtcID]->currentMode, mode, sizeof(display_mode));

	uint32 connectorIndex = gDisplay[crtcID]->connectorIndex;

	// Determine DP lanes if DP
	if (connector_is_dp(connectorIndex)) {
		dp_info *dpInfo = &gConnector[connectorIndex]->dpInfo;
		dpInfo->laneCount = dp_get_lane_count(connectorIndex, mode);
		dpInfo->linkRate = dp_get_link_rate(connectorIndex, mode);
	}

	// *** crtc and encoder prep
	encoder_output_lock(true);
	display_crtc_lock(crtcID, ATOM_ENABLE);
	radeon_dpms_set(crtcID, B_DPMS_OFF);

	// *** Set up encoder -> crtc routing
	encoder_assign_crtc(crtcID);

	// *** CRT controler mode set
	// Set up PLL for connector
	pll_pick(connectorIndex);
	pll_info* pll = &gConnector[connectorIndex]->encoder.pll;
	TRACE("%s: pll %d selected for connector %" B_PRIu32 "\n", __func__,
		pll->id, connectorIndex);
	pll_set(mode, crtcID);

	display_crtc_set_dtd(crtcID, mode);

	display_crtc_fb_set(crtcID, mode);
	// atombios_overscan_setup
	display_crtc_scale(crtcID, mode);

	// *** encoder mode set
	encoder_mode_set(crtcID);

	// *** encoder and CRT controller commit
	radeon_dpms_set(crtcID, B_DPMS_ON);
	display_crtc_lock(crtcID, ATOM_DISABLE);
	encoder_output_lock(false);

	#ifdef TRACE_MODE
	// for debugging
	debug_dp_info();

	TRACE("D1CRTC_STATUS        Value: 0x%X\n",
		Read32(CRT, AVIVO_D1CRTC_STATUS));
	TRACE("D2CRTC_STATUS        Value: 0x%X\n",
		Read32(CRT, AVIVO_D2CRTC_STATUS));
	TRACE("D1CRTC_CONTROL       Value: 0x%X\n",
		Read32(CRT, AVIVO_D1CRTC_CONTROL));
	TRACE("D2CRTC_CONTROL       Value: 0x%X\n",
		Read32(CRT, AVIVO_D2CRTC_CONTROL));
	TRACE("D1GRPH_ENABLE        Value: 0x%X\n",
		Read32(CRT, AVIVO_D1GRPH_ENABLE));
	TRACE("D2GRPH_ENABLE        Value: 0x%X\n",
		Read32(CRT, AVIVO_D2GRPH_ENABLE));
	TRACE("D1SCL_ENABLE         Value: 0x%X\n",
		Read32(CRT, AVIVO_D1SCL_SCALER_ENABLE));
	TRACE("D2SCL_ENABLE         Value: 0x%X\n",
		Read32(CRT, AVIVO_D2SCL_SCALER_ENABLE));
	TRACE("D1CRTC_BLANK_CONTROL Value: 0x%X\n",
		Read32(CRT, AVIVO_D1CRTC_BLANK_CONTROL));
	TRACE("D2CRTC_BLANK_CONTROL Value: 0x%X\n",
		Read32(CRT, AVIVO_D1CRTC_BLANK_CONTROL));
	#endif

	return B_OK;
}


status_t
radeon_get_display_mode(display_mode* _currentMode)
{
	TRACE("%s\n", __func__);

	*_currentMode = gInfo->shared_info->current_mode;
	//*_currentMode = gDisplay[X]->currentMode;
	return B_OK;
}


status_t
radeon_get_frame_buffer_config(frame_buffer_config* config)
{
	TRACE("%s\n", __func__);

	config->frame_buffer = gInfo->shared_info->frame_buffer;
	config->frame_buffer_dma = (uint8*)gInfo->shared_info->frame_buffer_phys;

	config->bytes_per_row = gInfo->shared_info->bytes_per_row;

	TRACE("  config->frame_buffer: %#" B_PRIxADDR "\n",
		(phys_addr_t)config->frame_buffer);
	TRACE("  config->frame_buffer_dma: %#" B_PRIxADDR "\n",
		(phys_addr_t)config->frame_buffer_dma);
	TRACE("  config->bytes_per_row: %" B_PRIu32 "\n", config->bytes_per_row);

	return B_OK;
}


status_t
radeon_get_pixel_clock_limits(display_mode* mode, uint32* _low, uint32* _high)
{
	TRACE("%s\n", __func__);

	if (_low != NULL) {
		// lower limit of about 48Hz vertical refresh
		uint32 totalClocks = (uint32)mode->timing.h_total
			* (uint32)mode->timing.v_total;
		uint32 low = (totalClocks * 48L) / 1000L;

		if (low < PLL_MIN_DEFAULT)
			low = PLL_MIN_DEFAULT;
		else if (low > PLL_MAX_DEFAULT)
			return B_ERROR;

		*_low = low;
	}

	if (_high != NULL)
		*_high = PLL_MAX_DEFAULT;

	//*_low = 48L;
	//*_high = 100 * 1000000L;
	return B_OK;
}


bool
is_mode_supported(display_mode* mode)
{
	bool sane = true;

	// Validate modeline is within a sane range
	if (is_mode_sane(mode) != B_OK)
		sane = false;

	// TODO: is_mode_supported on *which* display?
	uint32 crtid = 0;

	// if we have edid info, check frequency adginst crt reported valid ranges
	if (gInfo->shared_info->has_edid
		&& gDisplay[crtid]->foundRanges) {

		// validate horizontal frequency range
		uint32 hfreq = mode->timing.pixel_clock / mode->timing.h_total;
		if (hfreq > gDisplay[crtid]->hfreqMax + 1
			|| hfreq < gDisplay[crtid]->hfreqMin - 1) {
			//TRACE("!!! mode below falls outside of hfreq range!\n");
			sane = false;
		}

		// validate vertical frequency range
		uint32 vfreq = mode->timing.pixel_clock / ((mode->timing.v_total
			* mode->timing.h_total) / 1000);
		if (vfreq > gDisplay[crtid]->vfreqMax + 1
			|| vfreq < gDisplay[crtid]->vfreqMin - 1) {
			//TRACE("!!! mode below falls outside of vfreq range!\n");
			sane = false;
		}
	}

	#if 0
	// Lots of spam, but good for understanding what modelines are in use
	TRACE("MODE: %d ; %d %d %d %d ; %d %d %d %d is %s\n",
		mode->timing.pixel_clock, mode->timing.h_display,
		mode->timing.h_sync_start, mode->timing.h_sync_end,
		mode->timing.h_total, mode->timing.v_display,
		mode->timing.v_sync_start, mode->timing.v_sync_end,
		mode->timing.v_total,
		sane ? "OK." : "BAD, out of range!");
	#endif

	return sane;
}


/*
 * A quick sanity check of the provided display_mode
 */
status_t
is_mode_sane(display_mode* mode)
{
	// horizontal timing
	// validate h_sync_start is less then h_sync_end
	if (mode->timing.h_sync_start > mode->timing.h_sync_end) {
		TRACE("%s: ERROR: (%dx%d) "
			"received h_sync_start greater then h_sync_end!\n",
			__func__, mode->timing.h_display, mode->timing.v_display);
		return B_ERROR;
	}
	// validate h_total is greater then h_display
	if (mode->timing.h_total < mode->timing.h_display) {
		TRACE("%s: ERROR: (%dx%d) "
			"received h_total greater then h_display!\n",
			__func__, mode->timing.h_display, mode->timing.v_display);
		return B_ERROR;
	}

	// vertical timing
	// validate v_start is less then v_end
	if (mode->timing.v_sync_start > mode->timing.v_sync_end) {
		TRACE("%s: ERROR: (%dx%d) "
			"received v_sync_start greater then v_sync_end!\n",
			__func__, mode->timing.h_display, mode->timing.v_display);
		return B_ERROR;
	}
	// validate v_total is greater then v_display
	if (mode->timing.v_total < mode->timing.v_display) {
		TRACE("%s: ERROR: (%dx%d) "
			"received v_total greater then v_display!\n",
			__func__, mode->timing.h_display, mode->timing.v_display);
		return B_ERROR;
	}

	// calculate refresh rate for given timings to whole int (in Hz)
	int refresh = mode->timing.pixel_clock * 1000
		/ (mode->timing.h_total * mode->timing.v_total);

	if (refresh < 30 || refresh > 250) {
		TRACE("%s: ERROR: (%dx%d) "
			"refresh rate of %dHz is unlikely for any kind of monitor!\n",
			__func__, mode->timing.h_display, mode->timing.v_display, refresh);
		return B_ERROR;
	}

	return B_OK;
}


uint32
get_mode_bpp(display_mode* mode)
{
	// Get bitsPerPixel for given mode

	switch (mode->space) {
		case B_CMAP8:
			return 8;
		case B_RGB15_LITTLE:
			return 15;
		case B_RGB16_LITTLE:
			return 16;
		case B_RGB24_LITTLE:
		case B_RGB32_LITTLE:
			return 32;
	}
	ERROR("%s: Unknown colorspace for mode, guessing 32 bits per pixel\n",
		__func__);
	return 32;
}


static uint32_t
radeon_get_backlight_register()
{
	// R600 and up is 0x172c else its 0x0018
	if (gInfo->shared_info->chipsetID >= RADEON_R600)
		return 0x172c;
	return 0x0018;
}


status_t
radeon_set_brightness(float brightness)
{
	TRACE("%s (%f)\n", __func__, brightness);

	if (brightness < 0 || brightness > 1)
		return B_BAD_VALUE;

	uint32_t backlightReg = radeon_get_backlight_register();
	uint8_t brightnessRaw = (uint8_t)ceilf(brightness * 255);
	uint32_t level = Read32(OUT, backlightReg);
	TRACE("brightness level = %lx\n", level);
	level &= ~ATOM_S2_CURRENT_BL_LEVEL_MASK;
	level |= (( brightnessRaw << ATOM_S2_CURRENT_BL_LEVEL_SHIFT )
					& ATOM_S2_CURRENT_BL_LEVEL_MASK);
	TRACE("new brightness level = %lx\n", level);

	Write32(OUT, backlightReg, level);

	//TODO crtcID = 0: see create_mode
	// TODO: multi-monitor?  for now we use VESA and not gDisplay edid
	uint8 crtcID = 0;
	//TODO : test if it is a LCD ?
	uint32 connectorIndex = gDisplay[crtcID]->connectorIndex;
	connector_info* connector = gConnector[connectorIndex];
	pll_info* pll = &connector->encoder.pll;

	transmitter_dig_setup(connectorIndex, pll->pixelClock,
		0, 0, ATOM_TRANSMITTER_ACTION_BL_BRIGHTNESS_CONTROL);
	transmitter_dig_setup(connectorIndex, pll->pixelClock,
		0, 0, ATOM_TRANSMITTER_ACTION_LCD_BLON);

	return B_OK;
}


status_t
radeon_get_brightness(float* brightness)
{
	TRACE("%s\n", __func__);

	if (brightness == NULL)
		return B_BAD_VALUE;

	uint32_t backlightReg = Read32(OUT, radeon_get_backlight_register());
	uint8_t brightnessRaw = ((backlightReg & ATOM_S2_CURRENT_BL_LEVEL_MASK) >>
			ATOM_S2_CURRENT_BL_LEVEL_SHIFT);
	*brightness = (float)brightnessRaw / 255;

	return B_OK;
}