1/*
2 * Copyright 2005-2011, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Axel D��rfler, axeld@pinc-software.de
7 */
8
9
10#include "ScreenMode.h"
11
12#include <stdlib.h>
13#include <stdio.h>
14#include <string.h>
15
16#include <algorithm>
17
18#include <InterfaceDefs.h>
19#include <String.h>
20
21#include <compute_display_timing.h>
22
23
24/* Note, this headers defines a *private* interface to the Radeon accelerant.
25 * It's a solution that works with the current BeOS interface that Haiku
26 * adopted.
27 * However, it's not a nice and clean solution. Don't use this header in any
28 * application if you can avoid it. No other driver is using this, or should
29 * be using this.
30 * It will be replaced as soon as we introduce an updated accelerant interface
31 * which may even happen before R1 hits the streets.
32 */
33
34#include "multimon.h"	// the usual: DANGER WILL, ROBINSON!
35
36
37
38// Vendors.h is generated using these commands (plus a bit of manual editing):
39/*
40 * wget https://uefi.org/uefi-pnp-export -O Vendors.h.tmp
41 * if [ $? -eq 0 ]; then
42 *	sed -E -e 's:<thead>::g' -e 's:<tr.*="(.+)"><td>:{ ":g' -e 's:<td>[[:digit:]]{2}/[[:digit:]]{2}/[[:digit:]]{4}</td>::g' -e 's: *(<\/td><td>):\", \":g' -e 's: *<\/td>.<\/tr>:\" },:g' -e 's/"(.*?)", "(.*?)"/\"\2\", \"\1\"/' -e 's/&amp;/\&/' -e "s/&#039;/'/" Vendors.h.tmp | grep -v '<' | sort > Vendors.h
43 * fi
44 * rm Vendors.h.tmp
45 */
46
47struct pnp_id {
48	const char* id;
49	const char* manufacturer;
50	bool operator==(const pnp_id& a) const {
51		return ::strcasecmp(a.id, id) == 0;
52	};
53	bool operator()(const pnp_id& a, const pnp_id& b) const {
54		return ::strcasecmp(a.id, b.id) < 0;
55	};
56};
57
58static const struct pnp_id kPNPIDs[] = {
59#include "Vendors.h"
60};
61
62
63static combine_mode
64get_combine_mode(display_mode& mode)
65{
66	if ((mode.flags & B_SCROLL) == 0)
67		return kCombineDisable;
68
69	if (mode.virtual_width == mode.timing.h_display * 2)
70		return kCombineHorizontally;
71
72	if (mode.virtual_height == mode.timing.v_display * 2)
73		return kCombineVertically;
74
75	return kCombineDisable;
76}
77
78
79static float
80get_refresh_rate(display_mode& mode)
81{
82	// we have to be catious as refresh rate cannot be controlled directly,
83	// so it suffers under rounding errors and hardware restrictions
84	return rint(10 * float(mode.timing.pixel_clock * 1000)
85		/ float(mode.timing.h_total * mode.timing.v_total)) / 10.0;
86}
87
88
89/*!	Helper to sort modes by resolution */
90static int
91compare_mode(const void* _mode1, const void* _mode2)
92{
93	display_mode *mode1 = (display_mode *)_mode1;
94	display_mode *mode2 = (display_mode *)_mode2;
95	combine_mode combine1, combine2;
96	uint16 width1, width2, height1, height2;
97
98	combine1 = get_combine_mode(*mode1);
99	combine2 = get_combine_mode(*mode2);
100
101	width1 = mode1->virtual_width;
102	height1 = mode1->virtual_height;
103	width2 = mode2->virtual_width;
104	height2 = mode2->virtual_height;
105
106	if (combine1 == kCombineHorizontally)
107		width1 /= 2;
108	if (combine1 == kCombineVertically)
109		height1 /= 2;
110	if (combine2 == kCombineHorizontally)
111		width2 /= 2;
112	if (combine2 == kCombineVertically)
113		height2 /= 2;
114
115	if (width1 != width2)
116		return width1 - width2;
117
118	if (height1 != height2)
119		return height1 - height2;
120
121	return (int)(10 * get_refresh_rate(*mode1)
122		-  10 * get_refresh_rate(*mode2));
123}
124
125
126//	#pragma mark -
127
128
129int32
130screen_mode::BitsPerPixel() const
131{
132	switch (space) {
133		case B_RGB32:	return 32;
134		case B_RGB24:	return 24;
135		case B_RGB16:	return 16;
136		case B_RGB15:	return 15;
137		case B_CMAP8:	return 8;
138		default:		return 0;
139	}
140}
141
142
143bool
144screen_mode::operator==(const screen_mode &other) const
145{
146	return !(*this != other);
147}
148
149
150bool
151screen_mode::operator!=(const screen_mode &other) const
152{
153	return width != other.width || height != other.height
154		|| space != other.space || refresh != other.refresh
155		|| combine != other.combine
156		|| swap_displays != other.swap_displays
157		|| use_laptop_panel != other.use_laptop_panel
158		|| tv_standard != other.tv_standard;
159}
160
161
162void
163screen_mode::SetTo(display_mode& mode)
164{
165	width = mode.virtual_width;
166	height = mode.virtual_height;
167	space = (color_space)mode.space;
168	combine = get_combine_mode(mode);
169	refresh = get_refresh_rate(mode);
170
171	if (combine == kCombineHorizontally)
172		width /= 2;
173	else if (combine == kCombineVertically)
174		height /= 2;
175
176	swap_displays = false;
177	use_laptop_panel = false;
178	tv_standard = 0;
179}
180
181
182//	#pragma mark -
183
184
185ScreenMode::ScreenMode(BWindow* window)
186	:
187	fWindow(window),
188	fUpdatedModes(false)
189{
190	BScreen screen(window);
191	if (screen.GetModeList(&fModeList, &fModeCount) == B_OK) {
192		// sort modes by resolution and refresh to make
193		// the resolution and refresh menu look nicer
194		qsort(fModeList, fModeCount, sizeof(display_mode), compare_mode);
195	} else {
196		fModeList = NULL;
197		fModeCount = 0;
198	}
199}
200
201
202ScreenMode::~ScreenMode()
203{
204	free(fModeList);
205}
206
207
208status_t
209ScreenMode::Set(const screen_mode& mode, int32 workspace)
210{
211	if (!fUpdatedModes)
212		UpdateOriginalModes();
213
214	BScreen screen(fWindow);
215
216	if (workspace == ~0)
217		workspace = current_workspace();
218
219	// TODO: our app_server doesn't fully support workspaces, yet
220	SetSwapDisplays(&screen, mode.swap_displays);
221	SetUseLaptopPanel(&screen, mode.use_laptop_panel);
222	SetTVStandard(&screen, mode.tv_standard);
223
224	display_mode displayMode;
225	if (!_GetDisplayMode(mode, displayMode))
226		return B_ENTRY_NOT_FOUND;
227
228	return screen.SetMode(workspace, &displayMode, true);
229}
230
231
232status_t
233ScreenMode::Get(screen_mode& mode, int32 workspace) const
234{
235	display_mode displayMode;
236	BScreen screen(fWindow);
237
238	if (workspace == ~0)
239		workspace = current_workspace();
240
241	if (screen.GetMode(workspace, &displayMode) != B_OK)
242		return B_ERROR;
243
244	mode.SetTo(displayMode);
245
246	// TODO: our app_server doesn't fully support workspaces, yet
247	if (GetSwapDisplays(&screen, &mode.swap_displays) != B_OK)
248		mode.swap_displays = false;
249	if (GetUseLaptopPanel(&screen, &mode.use_laptop_panel) != B_OK)
250		mode.use_laptop_panel = false;
251	if (GetTVStandard(&screen, &mode.tv_standard) != B_OK)
252		mode.tv_standard = 0;
253
254	return B_OK;
255}
256
257
258status_t
259ScreenMode::GetOriginalMode(screen_mode& mode, int32 workspace) const
260{
261	if (workspace == ~0)
262		workspace = current_workspace();
263		// TODO this should use kMaxWorkspaces
264	else if (workspace > 31)
265		return B_BAD_INDEX;
266
267	mode = fOriginal[workspace];
268
269	return B_OK;
270}
271
272
273status_t
274ScreenMode::Set(const display_mode& mode, int32 workspace)
275{
276	if (!fUpdatedModes)
277		UpdateOriginalModes();
278
279	BScreen screen(fWindow);
280
281	if (workspace == ~0)
282		workspace = current_workspace();
283
284	// BScreen::SetMode() needs a non-const display_mode
285	display_mode nonConstMode;
286	memcpy(&nonConstMode, &mode, sizeof(display_mode));
287	return screen.SetMode(workspace, &nonConstMode, true);
288}
289
290
291status_t
292ScreenMode::Get(display_mode& mode, int32 workspace) const
293{
294	BScreen screen(fWindow);
295
296	if (workspace == ~0)
297		workspace = current_workspace();
298
299	return screen.GetMode(workspace, &mode);
300}
301
302
303/*!	This method assumes that you already reverted to the correct number
304	of workspaces.
305*/
306status_t
307ScreenMode::Revert()
308{
309	if (!fUpdatedModes)
310		return B_ERROR;
311
312	status_t result = B_OK;
313	screen_mode current;
314	for (int32 workspace = 0; workspace < count_workspaces(); workspace++) {
315		if (Get(current, workspace) == B_OK && fOriginal[workspace] == current)
316			continue;
317
318		BScreen screen(fWindow);
319
320		// TODO: our app_server doesn't fully support workspaces, yet
321		if (workspace == current_workspace()) {
322			SetSwapDisplays(&screen, fOriginal[workspace].swap_displays);
323			SetUseLaptopPanel(&screen, fOriginal[workspace].use_laptop_panel);
324			SetTVStandard(&screen, fOriginal[workspace].tv_standard);
325		}
326
327		result = screen.SetMode(workspace, &fOriginalDisplayMode[workspace],
328			true);
329		if (result != B_OK)
330			break;
331	}
332
333	return result;
334}
335
336
337void
338ScreenMode::UpdateOriginalModes()
339{
340	BScreen screen(fWindow);
341	for (int32 workspace = 0; workspace < count_workspaces(); workspace++) {
342		if (screen.GetMode(workspace, &fOriginalDisplayMode[workspace])
343				== B_OK) {
344			Get(fOriginal[workspace], workspace);
345			fUpdatedModes = true;
346		}
347	}
348}
349
350
351bool
352ScreenMode::SupportsColorSpace(const screen_mode& mode, color_space space)
353{
354	return true;
355}
356
357
358status_t
359ScreenMode::GetRefreshLimits(const screen_mode& mode, float& min, float& max)
360{
361	uint32 minClock, maxClock;
362	display_mode displayMode;
363	if (!_GetDisplayMode(mode, displayMode))
364		return B_ERROR;
365
366	BScreen screen(fWindow);
367	if (screen.GetPixelClockLimits(&displayMode, &minClock, &maxClock) < B_OK)
368		return B_ERROR;
369
370	uint32 total = displayMode.timing.h_total * displayMode.timing.v_total;
371	min = minClock * 1000.0 / total;
372	max = maxClock * 1000.0 / total;
373
374	return B_OK;
375}
376
377
378const char*
379ScreenMode::GetManufacturerFromID(const char* id) const
380{
381	// We assume the array is sorted
382	const size_t numElements = B_COUNT_OF(kPNPIDs);
383	const struct pnp_id key = { id, "dummy" };
384	const pnp_id* lastElement = kPNPIDs + numElements;
385	const pnp_id* element = std::find(kPNPIDs, lastElement, key);
386	if (element == lastElement) {
387		// can't find the vendor code
388		return NULL;
389	}
390
391	return element->manufacturer;
392}
393
394
395status_t
396ScreenMode::GetMonitorInfo(monitor_info& info, float* _diagonalInches)
397{
398	BScreen screen(fWindow);
399	status_t status = screen.GetMonitorInfo(&info);
400	if (status != B_OK)
401		return status;
402
403	if (_diagonalInches != NULL) {
404		*_diagonalInches = round(sqrt(info.width * info.width
405			+ info.height * info.height) / 0.254) / 10.0;
406	}
407
408	// Some older CRT monitors do not contain the monitor range information
409	// (EDID1_MONITOR_RANGES) in their EDID info resulting in the min/max
410	// horizontal/vertical frequencies being zero.  In this case, set the
411	// vertical frequency range to 60..85 Hz.
412	if (info.min_vertical_frequency == 0) {
413		info.min_vertical_frequency = 60;
414		info.max_vertical_frequency = 85;
415	}
416
417	char vendor[4];
418	strlcpy(vendor, info.vendor, sizeof(vendor));
419	const char* vendorString = GetManufacturerFromID(vendor);
420	if (vendorString != NULL)
421		strlcpy(info.vendor, vendorString, sizeof(info.vendor));
422
423	// Remove extraneous vendor strings and whitespace
424
425	BString name(info.name);
426	name.IReplaceAll(info.vendor, "");
427	name.Trim();
428
429	strcpy(info.name, name.String());
430
431	return B_OK;
432}
433
434
435status_t
436ScreenMode::GetDeviceInfo(accelerant_device_info& info)
437{
438	BScreen screen(fWindow);
439	return screen.GetDeviceInfo(&info);
440}
441
442
443screen_mode
444ScreenMode::ModeAt(int32 index)
445{
446	if (index < 0)
447		index = 0;
448	else if (index >= (int32)fModeCount)
449		index = fModeCount - 1;
450
451	screen_mode mode;
452	mode.SetTo(fModeList[index]);
453
454	return mode;
455}
456
457
458const display_mode&
459ScreenMode::DisplayModeAt(int32 index)
460{
461	if (index < 0)
462		index = 0;
463	else if (index >= (int32)fModeCount)
464		index = fModeCount - 1;
465
466	return fModeList[index];
467}
468
469
470int32
471ScreenMode::CountModes()
472{
473	return fModeCount;
474}
475
476
477/*!	Searches for a similar mode in the reported mode list, and if that does not
478	find a matching mode, it will compute the mode manually using the GTF.
479*/
480bool
481ScreenMode::_GetDisplayMode(const screen_mode& mode, display_mode& displayMode)
482{
483	uint16 virtualWidth, virtualHeight;
484	int32 bestIndex = -1;
485	float bestDiff = 999;
486
487	virtualWidth = mode.combine == kCombineHorizontally
488		? mode.width * 2 : mode.width;
489	virtualHeight = mode.combine == kCombineVertically
490		? mode.height * 2 : mode.height;
491
492	// try to find mode in list provided by driver
493	for (uint32 i = 0; i < fModeCount; i++) {
494		if (fModeList[i].virtual_width != virtualWidth
495			|| fModeList[i].virtual_height != virtualHeight
496			|| (color_space)fModeList[i].space != mode.space)
497			continue;
498
499		// Accept the mode if the computed refresh rate of the mode is within
500		// 0.6 percent of the refresh rate specified by the caller.  Note that
501		// refresh rates computed from mode parameters is not exact; especially
502		// some of the older modes such as 640x480, 800x600, and 1024x768.
503		// The tolerance of 0.6% was obtained by examining the various possible
504		// modes.
505
506		float refreshDiff = fabs(get_refresh_rate(fModeList[i]) - mode.refresh);
507		if (refreshDiff < 0.006 * mode.refresh) {
508			// Accept this mode.
509			displayMode = fModeList[i];
510			displayMode.h_display_start = 0;
511			displayMode.v_display_start = 0;
512
513			// Since the computed refresh rate of the selected mode might differ
514			// from selected refresh rate by a few tenths (e.g. 60.2 instead of
515			// 60.0), tweak the pixel clock so the the refresh rate of the mode
516			// matches the selected refresh rate.
517
518			displayMode.timing.pixel_clock = uint32(((displayMode.timing.h_total
519				* displayMode.timing.v_total * mode.refresh) / 1000.0) + 0.5);
520			return true;
521		}
522
523		// Mode not acceptable.
524
525		if (refreshDiff < bestDiff) {
526			bestDiff = refreshDiff;
527			bestIndex = i;
528		}
529	}
530
531	// we didn't find the exact mode, but something very similar?
532	if (bestIndex == -1)
533		return false;
534
535	displayMode = fModeList[bestIndex];
536	displayMode.h_display_start = 0;
537	displayMode.v_display_start = 0;
538
539	// For the mode selected by the width, height, and refresh rate, compute
540	// the video timing parameters for the mode by using the VESA Generalized
541	// Timing Formula (GTF).
542	compute_display_timing(mode.width, mode.height, mode.refresh, false,
543		&displayMode.timing);
544
545	return true;
546}
547