1/*
2	Copyright 2010 Haiku, Inc.  All rights reserved.
3	Distributed under the terms of the MIT license.
4
5	Authors:
6	Gerald Zajac
7*/
8
9/*
10	Some of the code in this source file was adapted from the X.org tdfx
11	video driver, and was covered by the following copyright and license.
12	--------------------------------------------------------------------------
13
14	Copyright 1998-1999 Precision Insight, Inc., Cedar Park, Texas.
15	All Rights Reserved.
16
17	Permission is hereby granted, free of charge, to any person obtaining a
18	copy of this software and associated documentation files (the
19	"Software"), to deal in the Software without restriction, including
20	without limitation the rights to use, copy, modify, merge, publish,
21	distribute, sub license, and/or sell copies of the Software, and to
22	permit persons to whom the Software is furnished to do so, subject to
23	the following conditions:
24
25	The above copyright notice and this permission notice (including the
26	next paragraph) shall be included in all copies or substantial portions
27	of the Software.
28
29	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
30	OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
32	IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR
33	ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
34	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
35	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36*/
37
38#include "accelerant.h"
39#include "3dfx.h"
40
41#include <stdlib.h>
42#include <unistd.h>
43
44
45// Functions to read/write PIO registers.
46//=======================================
47
48static void
49WritePIOReg(uint32 offset, int16 index, uint8 value)
50{
51	PIORegInfo prInfo;
52	prInfo.magic = TDFX_PRIVATE_DATA_MAGIC;
53	prInfo.offset = offset;
54	prInfo.index = index;
55	prInfo.value = value;
56
57	status_t result = ioctl(gInfo.deviceFileDesc, TDFX_SET_PIO_REG,
58		&prInfo, sizeof(prInfo));
59	if (result != B_OK)
60		TRACE("WritePIOReg() failed, result = 0x%x\n", result);
61}
62
63
64static uint8
65ReadPIOReg(uint32 offset, int16 index)
66{
67	PIORegInfo prInfo;
68	prInfo.magic = TDFX_PRIVATE_DATA_MAGIC;
69	prInfo.offset = offset;
70	prInfo.index = index;
71
72	status_t result = ioctl(gInfo.deviceFileDesc, TDFX_GET_PIO_REG,
73		&prInfo, sizeof(prInfo));
74	if (result != B_OK)
75		TRACE("ReadPIOReg() failed, result = 0x%x\n", result);
76
77	return prInfo.value;
78}
79
80
81static void
82WriteMiscOutReg(uint8 value)
83{
84	WritePIOReg(MISC_OUT_W - 0x300, -1, value);
85}
86
87
88static void
89WriteCrtcReg(uint8 index, uint8 value)
90{
91	WritePIOReg(CRTC_INDEX - 0x300, index, value);
92}
93
94
95uint8
96ReadMiscOutReg()
97{
98	return ReadPIOReg(MISC_OUT_R - 0x300, -1);
99}
100
101
102uint8
103ReadCrtcReg(uint8 index)
104{
105	return ReadPIOReg(CRTC_INDEX - 0x300, index);
106}
107
108
109void
110TDFX_WaitForFifo(uint32 entries)
111{
112	// The FIFO has 32 slots.  This routine waits until at least `entries'
113	// of these slots are empty.
114
115	while ((INREG32(STATUS) & 0x1f) < entries) ;
116}
117
118
119void
120TDFX_WaitForIdle()
121{
122	// Wait for the graphics engine to be completely idle.
123
124	TDFX_WaitForFifo(1);
125	OUTREG32(CMD_3D, CMD_3D_NOP);
126
127	int i = 0;
128
129	do {
130		i = (INREG32(STATUS) & STATUS_BUSY) ? 0 : i + 1;
131	} while (i < 3);
132}
133
134
135static int
136TDFX_CalcPLL(int freq)
137{
138	const int refFreq = 14318;
139	int best_error = freq;
140	int best_k = 0;
141	int best_m = 0;
142	int best_n = 0;
143
144	for (int n = 1; n < 256; n++) {
145		int freqCur = refFreq * (n + 2);
146		if (freqCur < freq) {
147			freqCur = freqCur / 3;
148			if (freq - freqCur < best_error) {
149				best_error = freq - freqCur;
150				best_n = n;
151				best_m = 1;
152				best_k = 0;
153				continue;
154			}
155		}
156		for (int m = 1; m < 64; m++) {
157			for (int k = 0; k < 4; k++) {
158				freqCur = refFreq * (n + 2) / (m + 2) / (1 << k);
159				if (abs(freqCur - freq) < best_error) {
160					best_error = abs(freqCur - freq);
161					best_n = n;
162					best_m = m;
163					best_k = k;
164				}
165			}
166		}
167	}
168
169	return (best_n << 8) | (best_m << 2) | best_k;
170}
171
172
173bool
174TDFX_GetColorSpaceParams(int colorSpace, uint8& bitsPerPixel)
175{
176	// Get parameters for a color space which is supported by the 3dfx chips.
177	// Return true if the color space is supported;  else return false.
178
179	switch (colorSpace) {
180		case B_RGB32:
181			bitsPerPixel = 32;
182			break;
183		case B_RGB16:
184			bitsPerPixel = 16;
185			break;
186		case B_RGB15:
187			bitsPerPixel = 15;
188			break;
189		case B_CMAP8:
190			bitsPerPixel = 8;
191			break;
192		default:
193			TRACE("Unsupported color space: 0x%X\n", colorSpace);
194			return false;
195	}
196
197	return true;
198}
199
200
201status_t
202TDFX_SetDisplayMode(const DisplayModeEx& mode)
203{
204	// The code to actually configure the display.
205	// All the error checking must be done in ProposeDisplayMode(),
206	// and assume that the mode values we get here are acceptable.
207
208	SharedInfo& si = *gInfo.sharedInfo;
209	bool clock2X = mode.timing.pixel_clock > si.maxPixelClock / 2;
210
211	// Initialize the timing values for CRTC registers cr00 to cr18.  Note
212	// that the number following the letters 'cr' is a hexadecimal number.
213	// Argument crtc will contain registers cr00 to cr18;  thus, it must
214	// contain at least 25 (0x19) elements.
215
216	// Normally the horizontal timing values are divided by 8;  however,
217	// if the clock is above the 2X value, divide by 16 such that the values
218	// are halved.
219
220	int horzDiv = clock2X ? 16 : 8;
221
222	int hTotal = mode.timing.h_total / horzDiv - 5;
223	int hDisp_e = mode.timing.h_display / horzDiv - 1;
224	int hSync_s = mode.timing.h_sync_start / horzDiv;
225	int hSync_e = mode.timing.h_sync_end / horzDiv;
226	int hBlank_s = hDisp_e + 1;		// start of horizontal blanking
227	int hBlank_e = hTotal + 3;		// end of horizontal blanking
228
229	int vTotal = mode.timing.v_total - 2;
230	int vDisp_e = mode.timing.v_display - 1;
231	int vSync_s = mode.timing.v_sync_start;
232	int vSync_e = mode.timing.v_sync_end;
233	int vBlank_s = vDisp_e + 1;		// start of vertical blanking
234	int vBlank_e = vTotal;			// end of vertical blanking
235
236	// CRTC Controller values
237
238	uint8 crtc[25];
239
240	crtc[0x00] = hTotal;
241	crtc[0x01] = hDisp_e;
242	crtc[0x02] = hBlank_s;
243	crtc[0x03] = (hBlank_e & 0x1f) | 0x80;
244	crtc[0x04] = hSync_s;
245	crtc[0x05] = ((hSync_e & 0x1f) | ((hBlank_e & 0x20) << 2));
246	crtc[0x06] = vTotal;
247	crtc[0x07] = (((vTotal & 0x100) >> 8)
248		| ((vDisp_e & 0x100) >> 7)
249		| ((vSync_s & 0x100) >> 6)
250		| ((vBlank_s & 0x100) >> 5)
251		| 0x10
252		| ((vTotal & 0x200) >> 4)
253		| ((vDisp_e & 0x200) >> 3)
254		| ((vSync_s & 0x200) >> 2));
255	crtc[0x08] = 0x00;
256	crtc[0x09] = ((vBlank_s & 0x200) >> 4) | 0x40;
257	crtc[0x0a] = 0x00;
258	crtc[0x0b] = 0x00;
259	crtc[0x0c] = 0x00;
260	crtc[0x0d] = 0x00;
261	crtc[0x0e] = 0x00;
262	crtc[0x0f] = 0x00;
263	crtc[0x10] = vSync_s;
264	crtc[0x11] = (vSync_e & 0x0f) | 0x20;
265	crtc[0x12] = vDisp_e;
266	crtc[0x13] = hDisp_e + 1;
267	crtc[0x14] = 0x00;
268	crtc[0x15] = vBlank_s;
269	crtc[0x16] = vBlank_e;
270	crtc[0x17] = 0xc3;
271	crtc[0x18] = 0xff;
272
273	// Set up the extra CR reg's to handle the higher resolution modes.
274
275	uint8 cr1a = (hTotal & 0x100) >> 8
276				| (hDisp_e & 0x100) >> 6
277				| (hBlank_s & 0x100) >> 4
278				| (hBlank_e & 0x40) >> 1
279				| (hSync_s & 0x100) >> 2
280				| (hSync_e & 0x20) << 2;
281
282	uint8 cr1b = (vTotal & 0x400) >> 10
283				| (vDisp_e & 0x400) >> 8
284				| (vBlank_s & 0x400) >> 6
285				| (vBlank_e & 0x400) >> 4;
286
287	uint8 miscOutReg = 0x0f | (mode.timing.v_display < 400 ? 0xa0
288							: mode.timing.v_display < 480 ? 0x60
289							: mode.timing.v_display < 768 ? 0xe0 : 0x20);
290
291	uint32 vgaInit0 = VGA0_EXTENSIONS
292					| WAKEUP_3C3
293					| ENABLE_ALT_READBACK
294					| CLUT_SELECT_8BIT
295					| EXT_SHIFT_OUT;
296
297	uint32 videoConfig = VIDEO_PROCESSOR_ENABLE | DESKTOP_ENABLE
298					| (mode.bytesPerPixel - 1) << DESKTOP_PIXEL_FORMAT_SHIFT
299					| (mode.bytesPerPixel > 1 ? DESKTOP_CLUT_BYPASS : 0);
300
301	uint32 dacMode = INREG32(DAC_MODE) & ~DAC_MODE_2X;
302
303	if (clock2X) {
304		dacMode |= DAC_MODE_2X;
305		videoConfig |= VIDEO_2X_MODE_ENABLE;
306	}
307
308	uint32 pllFreq = TDFX_CalcPLL(mode.timing.pixel_clock);
309
310	// Note that for the Banshee chip, the mode 1280x1024 at 60Hz refresh does
311	// not display properly using the computed PLL frequency;  thus, set it to
312	// the value that is computed when set by VESA.
313
314	if (si.chipType == BANSHEE && pllFreq == 45831
315			&& mode.timing.h_display == 1280 && mode.timing.v_display == 1024)
316		pllFreq = 45912;
317
318	uint32 screenSize = mode.timing.h_display | (mode.timing.v_display << 12);
319
320	// Now that the values for the registers have been computed, write the
321	// registers to set the mode.
322	//=====================================================================
323
324	TDFX_WaitForFifo(2);
325	OUTREG32(VIDEO_PROC_CONFIG, 0);
326	OUTREG32(PLL_CTRL0, pllFreq);
327
328	WriteMiscOutReg(miscOutReg);
329
330	for (uint8 j = 0; j < 25; j++)
331		WriteCrtcReg(j, crtc[j]);
332
333	WriteCrtcReg(0x1a, cr1a);
334	WriteCrtcReg(0x1b, cr1b);
335
336	TDFX_WaitForFifo(6);
337	OUTREG32(VGA_INIT0, vgaInit0);
338	OUTREG32(DAC_MODE, dacMode);
339	OUTREG32(VIDEO_DESKTOP_OVERLAY_STRIDE, mode.bytesPerRow);
340	OUTREG32(HW_CURSOR_PAT_ADDR, si.cursorOffset);
341	OUTREG32(VIDEO_SCREEN_SIZE, screenSize);
342	OUTREG32(VIDEO_DESKTOP_START_ADDR, si.frameBufferOffset);
343
344	TDFX_WaitForFifo(7);
345	OUTREG32(CLIP0_MIN, 0);
346	OUTREG32(CLIP0_MAX, 0x0fff0fff);
347	OUTREG32(CLIP1_MIN, 0);
348	OUTREG32(CLIP1_MAX, 0x0fff0fff);
349	OUTREG32(VIDEO_PROC_CONFIG, videoConfig);
350	OUTREG32(SRC_BASE_ADDR, si.frameBufferOffset);
351	OUTREG32(DST_BASE_ADDR, si.frameBufferOffset);
352
353	TDFX_WaitForIdle();
354
355	TDFX_AdjustFrame(mode);
356
357	return B_OK;
358}
359
360
361void
362TDFX_AdjustFrame(const DisplayModeEx& mode)
363{
364	// Adjust start address in frame buffer.
365
366	SharedInfo& si = *gInfo.sharedInfo;
367
368	int address = (mode.v_display_start * mode.virtual_width
369			+ mode.h_display_start) * mode.bytesPerPixel;
370
371	address &= ~0x07;
372	address += si.frameBufferOffset;
373
374	TDFX_WaitForFifo(1);
375	OUTREG32(VIDEO_DESKTOP_START_ADDR, address);
376	return;
377}
378
379
380void
381TDFX_SetIndexedColors(uint count, uint8 first, uint8* colorData, uint32 flags)
382{
383	// Set the indexed color palette for 8-bit color depth mode.
384
385	(void)flags;		// avoid compiler warning for unused arg
386
387	if (gInfo.sharedInfo->displayMode.space != B_CMAP8)
388		return ;
389
390	uint32 index = first;
391
392	while (count--) {
393		uint32 color = ((colorData[0] << 16) | (colorData[1] << 8) | colorData[2]);
394		TDFX_WaitForFifo(2);
395		OUTREG32(DAC_ADDR, index++);
396		INREG32(DAC_ADDR);		// color not always set unless we read after write
397		OUTREG32(DAC_DATA, color);
398
399		colorData += 3;
400	}
401}
402