1/*
2	Copyright 2007-2009 Haiku, Inc.  All rights reserved.
3	Distributed under the terms of the MIT license.
4
5	Authors:
6	Gerald Zajac 2007-2009
7*/
8
9#include "accelerant.h"
10
11#include <string.h>
12#include <unistd.h>
13
14#include <create_display_modes.h>		// common accelerant header file
15
16
17static display_mode*
18FindDisplayMode(int width, int height, int refreshRate, uint32 colorDepth)
19{
20	// Search the mode list for the mode with specified width, height,
21	// refresh rate, and color depth.  If argument colorDepth is zero, this
22	// function will search for a mode satisfying the other 3 arguments, and
23	// if more than one matching mode is found, the one with the greatest color
24	// depth will be selected.
25	//
26	// If successful, return a pointer to the selected display_mode object;
27	// else return NULL.
28
29	display_mode* selectedMode = NULL;
30	uint32 modeCount = gInfo.sharedInfo->modeCount;
31
32	for (uint32 j = 0; j < modeCount; j++) {
33		display_mode& mode = gInfo.modeList[j];
34
35		if (mode.timing.h_display == width && mode.timing.v_display == height) {
36			int modeRefreshRate = int(((mode.timing.pixel_clock * 1000.0 /
37					mode.timing.h_total) / mode.timing.v_total) + 0.5);
38			if (modeRefreshRate == refreshRate) {
39				if (colorDepth == 0) {
40					if (selectedMode == NULL || mode.space > selectedMode->space)
41						selectedMode = &mode;
42				} else {
43					if (mode.space == colorDepth)
44						return &mode;
45				}
46			}
47		}
48	}
49
50	return selectedMode;
51}
52
53
54
55static bool
56IsThereEnoughFBMemory(const display_mode* mode, uint32 bitsPerPixel)
57{
58	// Test if there is enough Frame Buffer memory for the mode and color depth
59	// specified by the caller, and return true if there is sufficient memory.
60
61	uint32 maxWidth = mode->virtual_width;
62	if (mode->timing.h_display > maxWidth)
63		maxWidth = mode->timing.h_display;
64
65	uint32 maxHeight = mode->virtual_height;
66	if (mode->timing.v_display > maxHeight)
67		maxHeight = mode->timing.v_display;
68
69	uint32 bytesPerPixel = (bitsPerPixel + 7) / 8;
70
71	return (maxWidth * maxHeight * bytesPerPixel < gInfo.sharedInfo->maxFrameBufferSize);
72}
73
74
75
76uint16
77GetVesaModeNumber(const display_mode& mode, uint8 bitsPerPixel)
78{
79	// Search VESA mode table for a matching mode, and return the VESA mode
80	// number if a match is found;  else return 0 if no match is found.
81
82	SharedInfo& si = *gInfo.sharedInfo;
83
84	VesaMode* vesaModeTable = (VesaMode*)((uint8*)&si + si.vesaModeTableOffset);
85
86	for (uint32 j = 0; j < si.vesaModeCount; j++) {
87		VesaMode& vesaMode = vesaModeTable[j];
88
89		if (vesaMode.width == mode.timing.h_display
90			&& vesaMode.height == mode.timing.v_display
91			&& vesaMode.bitsPerPixel == bitsPerPixel)
92			return vesaMode.mode;
93	}
94
95	return 0;		// matching VESA mode not found
96}
97
98
99
100bool
101IsModeUsable(const display_mode* mode)
102{
103	// Test if the display mode is usable by the current video chip.  That is,
104	// does the chip have enough memory for the mode and is the pixel clock
105	// within the chips allowable range, etc.
106	//
107	// Return true if the mode is usable.
108
109	SharedInfo& si = *gInfo.sharedInfo;
110	uint8 bitsPerPixel;
111	uint32 maxPixelClock;
112
113	if (!gInfo.GetColorSpaceParams(mode->space, bitsPerPixel, maxPixelClock))
114		return false;
115
116	// Is there enough frame buffer memory to handle the mode?
117
118	if (!IsThereEnoughFBMemory(mode, bitsPerPixel))
119		return false;
120
121	if (si.displayType == MT_VGA) {
122		// Test if mode is usable for a chip that is connected to a monitor
123		// via an analog VGA connector.
124
125		if (mode->timing.pixel_clock > maxPixelClock)
126			return false;
127
128		// Is the color space supported?
129
130		bool colorSpaceSupported = false;
131		for (uint32 j = 0; j < si.colorSpaceCount; j++) {
132			if (mode->space == uint32(si.colorSpaces[j])) {
133				colorSpaceSupported = true;
134				break;
135			}
136		}
137
138		if (!colorSpaceSupported)
139			return false;
140
141		// Reject modes with a width of 640 and a height < 480 since they do not
142		// work properly with the ATI chipsets.
143
144		if (mode->timing.h_display == 640 && mode->timing.v_display < 480)
145			return false;
146	} else {
147		// Test if mode is usable for a chip that is connected to a laptop LCD
148		// display or a monitor via a DVI interface.
149
150		// If chip is a Mach64 Mobility chip exclude 640x350 resolution since
151		// that resolution can not be set without a failure in the VESA set mode
152		// function.
153
154		if (si.chipType == MACH64_MOBILITY && mode->timing.h_display == 640
155				&& mode->timing.v_display == 350)
156			return false;
157
158		// Search VESA mode table for matching mode.
159
160		if (GetVesaModeNumber(*mode, bitsPerPixel) == 0)
161			return false;
162	}
163
164	return true;
165}
166
167
168status_t
169CreateModeList(bool (*checkMode)(const display_mode* mode))
170{
171	SharedInfo& si = *gInfo.sharedInfo;
172
173	// Obtain EDID info which is needed for for building the mode list.
174	// However, if a laptop's LCD display is active, bypass getting the EDID
175	// info since it is not needed, and if the external display supports only
176	// resolutions smaller than the size of the laptop LCD display, it would
177	// unnecessarily restrict size of the resolutions that could be set for
178	// laptop LCD display.
179
180	si.bHaveEDID = false;
181
182	if (si.displayType == MT_VGA && !si.bHaveEDID) {
183		edid1_raw rawEdid;	// raw EDID info to obtain
184
185		if (ioctl(gInfo.deviceFileDesc, ATI_GET_EDID, &rawEdid,
186				sizeof(rawEdid)) == B_OK) {
187			if (rawEdid.version.version != 1 || rawEdid.version.revision > 4) {
188				TRACE("CreateModeList(); EDID version %d.%d out of range\n",
189					rawEdid.version.version, rawEdid.version.revision);
190			} else {
191				edid_decode(&si.edidInfo, &rawEdid);	// decode & save EDID info
192				si.bHaveEDID = true;
193			}
194		}
195
196		if (si.bHaveEDID) {
197#ifdef ENABLE_DEBUG_TRACE
198			edid_dump(&(si.edidInfo));
199#endif
200		} else {
201			TRACE("CreateModeList(); Unable to get EDID info\n");
202		}
203	}
204
205	display_mode* list;
206	uint32 count = 0;
207	area_id listArea;
208
209	listArea = create_display_modes("ATI modes",
210		si.bHaveEDID ? &si.edidInfo : NULL,
211		NULL, 0, si.colorSpaces, si.colorSpaceCount,
212		(check_display_mode_hook)checkMode, &list, &count);
213
214	if (listArea < 0)
215		return listArea;		// listArea has error code
216
217	si.modeArea = gInfo.modeListArea = listArea;
218	si.modeCount = count;
219	gInfo.modeList = list;
220
221	return B_OK;
222}
223
224
225
226status_t
227ProposeDisplayMode(display_mode *target, const display_mode *low,
228	const display_mode *high)
229{
230	(void)low;		// avoid compiler warning for unused arg
231	(void)high;		// avoid compiler warning for unused arg
232
233	TRACE("ProposeDisplayMode()  %dx%d, pixel clock: %d kHz, space: 0x%X\n",
234		target->timing.h_display, target->timing.v_display,
235		target->timing.pixel_clock, target->space);
236
237	// Search the mode list for the specified mode.
238
239	uint32 modeCount = gInfo.sharedInfo->modeCount;
240
241	for (uint32 j = 0; j < modeCount; j++) {
242		display_mode& mode = gInfo.modeList[j];
243
244		if (target->timing.h_display == mode.timing.h_display
245			&& target->timing.v_display == mode.timing.v_display
246			&& target->space == mode.space)
247			return B_OK;	// mode found in list
248	}
249
250	return B_BAD_VALUE;		// mode not found in list
251}
252
253
254status_t
255SetDisplayMode(display_mode* pMode)
256{
257	// First validate the mode, then call a function to set the registers.
258
259	TRACE("SetDisplayMode() begin\n");
260
261	SharedInfo& si = *gInfo.sharedInfo;
262	DisplayModeEx mode;
263	(display_mode&)mode = *pMode;
264
265	uint32 maxPixelClock;
266	if ( ! gInfo.GetColorSpaceParams(mode.space, mode.bitsPerPixel, maxPixelClock))
267		return B_BAD_VALUE;
268
269	if (ProposeDisplayMode(&mode, pMode, pMode) != B_OK)
270		return B_BAD_VALUE;
271
272	int bytesPerPixel = (mode.bitsPerPixel + 7) / 8;
273	mode.bytesPerRow = mode.timing.h_display * bytesPerPixel;
274
275	// Is there enough frame buffer memory for this mode?
276
277	if ( ! IsThereEnoughFBMemory(&mode, mode.bitsPerPixel))
278		return B_NO_MEMORY;
279
280	TRACE("Set display mode: %dx%d  virtual size: %dx%d  color depth: %d bits/pixel\n",
281		mode.timing.h_display, mode.timing.v_display,
282		mode.virtual_width, mode.virtual_height, mode.bitsPerPixel);
283
284	if (si.displayType == MT_VGA) {
285		TRACE("   mode timing: %d  %d %d %d %d  %d %d %d %d\n",
286			mode.timing.pixel_clock,
287			mode.timing.h_display,
288			mode.timing.h_sync_start, mode.timing.h_sync_end,
289			mode.timing.h_total,
290			mode.timing.v_display,
291			mode.timing.v_sync_start, mode.timing.v_sync_end,
292			mode.timing.v_total);
293
294		TRACE("   mode hFreq: %.1f kHz  vFreq: %.1f Hz  %chSync %cvSync\n",
295			double(mode.timing.pixel_clock) / mode.timing.h_total,
296			((double(mode.timing.pixel_clock) / mode.timing.h_total) * 1000.0)
297			/ mode.timing.v_total,
298			(mode.timing.flags & B_POSITIVE_HSYNC) ? '+' : '-',
299			(mode.timing.flags & B_POSITIVE_VSYNC) ? '+' : '-');
300	}
301
302	status_t status = gInfo.SetDisplayMode(mode);
303	if (status != B_OK) {
304		TRACE("SetDisplayMode() failed;  status 0x%x\n", status);
305		return status;
306	}
307
308	si.displayMode = mode;
309
310	TRACE("SetDisplayMode() done\n");
311	return B_OK;
312}
313
314
315
316status_t
317MoveDisplay(uint16 horizontalStart, uint16 verticalStart)
318{
319	// Set which pixel of the virtual frame buffer will show up in the
320	// top left corner of the display device.	Used for page-flipping
321	// games and virtual desktops.
322
323	DisplayModeEx& mode = gInfo.sharedInfo->displayMode;
324
325	if (mode.timing.h_display + horizontalStart > mode.virtual_width
326		|| mode.timing.v_display + verticalStart > mode.virtual_height)
327		return B_ERROR;
328
329	mode.h_display_start = horizontalStart;
330	mode.v_display_start = verticalStart;
331
332	gInfo.AdjustFrame(mode);
333	return B_OK;
334}
335
336
337uint32
338AccelerantModeCount(void)
339{
340	// Return the number of display modes in the mode list.
341
342	return gInfo.sharedInfo->modeCount;
343}
344
345
346status_t
347GetModeList(display_mode* dmList)
348{
349	// Copy the list of supported video modes to the location pointed at
350	// by dmList.
351
352	memcpy(dmList, gInfo.modeList, gInfo.sharedInfo->modeCount * sizeof(display_mode));
353	return B_OK;
354}
355
356
357status_t
358GetDisplayMode(display_mode* current_mode)
359{
360	*current_mode = gInfo.sharedInfo->displayMode;	// return current display mode
361	return B_OK;
362}
363
364
365status_t
366GetFrameBufferConfig(frame_buffer_config* pFBC)
367{
368	SharedInfo& si = *gInfo.sharedInfo;
369
370	pFBC->frame_buffer = (void*)((addr_t)si.videoMemAddr + si.frameBufferOffset);
371	pFBC->frame_buffer_dma = (void*)((addr_t)si.videoMemPCI + si.frameBufferOffset);
372	uint32 bytesPerPixel = (si.displayMode.bitsPerPixel + 7) / 8;
373	pFBC->bytes_per_row = si.displayMode.virtual_width * bytesPerPixel;
374
375	return B_OK;
376}
377
378
379status_t
380GetPixelClockLimits(display_mode* mode, uint32* low, uint32* high)
381{
382	// Return the maximum and minium pixel clock limits for the specified mode.
383
384	uint8 bitsPerPixel;
385	uint32 maxPixelClock;
386
387	if ( ! gInfo.GetColorSpaceParams(mode->space, bitsPerPixel, maxPixelClock))
388		return B_ERROR;
389
390	if (low != NULL) {
391		// lower limit of about 48Hz vertical refresh
392		uint32 totalClocks = (uint32)mode->timing.h_total * (uint32)mode->timing.v_total;
393		uint32 lowClock = (totalClocks * 48L) / 1000L;
394		if (lowClock > maxPixelClock)
395			return B_ERROR;
396
397		*low = lowClock;
398	}
399
400	if (high != NULL)
401		*high = maxPixelClock;
402
403	return B_OK;
404}
405
406
407status_t
408GetTimingConstraints(display_timing_constraints *constraints)
409{
410	(void)constraints;		// avoid compiler warning for unused arg
411
412	return B_ERROR;
413}
414
415
416status_t
417GetPreferredDisplayMode(display_mode* preferredMode)
418{
419	// If the chip is connected to a laptop LCD panel, find the mode with
420	// matching width and height, 60 Hz refresh rate, and greatest color depth.
421
422	SharedInfo& si = *gInfo.sharedInfo;
423
424	if (si.displayType == MT_LAPTOP) {
425		display_mode* mode = FindDisplayMode(si.panelX, si.panelY, 60, 0);
426
427		if (mode != NULL) {
428			*preferredMode = *mode;
429			return B_OK;
430		}
431	}
432
433	return B_ERROR;
434}
435
436
437status_t
438GetEdidInfo(void* info, size_t size, uint32* _version)
439{
440	SharedInfo& si = *gInfo.sharedInfo;
441
442	if ( ! si.bHaveEDID)
443		return B_ERROR;
444
445	if (size < sizeof(struct edid1_info))
446		return B_BUFFER_OVERFLOW;
447
448	memcpy(info, &si.edidInfo, sizeof(struct edid1_info));
449	*_version = EDID_VERSION_1;
450	return B_OK;
451}
452