summaryrefslogtreecommitdiff
path: root/src/mga_bios.c
blob: e5aa45c3bd491aea67100820f487948755fa7840 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
/*
 * (C) Copyright IBM Corporation 2005
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * on the rights to use, copy, modify, merge, publish, distribute, sub
 * license, and/or sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
 * IBM AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/**
 * \file mga_bios.c
 * Routines for processing the PInS data stored in the MGA BIOS.
 *
 * The structure of this code was inspired by similar routines in the Linux
 * kernel matroxfb code.  Specifically, the routines in matroxfb_misc.c.  In
 * addition, that code was used in place of documentation about the structure
 * of the PInS data for non-G450 cards.
 *
 * \author Ian Romanick <idr@us.ibm.com>
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/* All drivers should typically include these */
#include "xf86.h"
#include "xf86_OSproc.h"

/* All drivers need this */

#include "compiler.h"

/* Drivers that need to access the PCI config space directly need this */
#include "xf86Pci.h"

#include <X11/Xmd.h>

#include "mga.h"

#if defined(DEBUG)
#define BIOS_DEBUG
#endif

/**
 * Read a little-endian, unaligned data value and return as 16-bit.
 */
static __inline__ CARD16 get_u16( const CARD8 * data )
{
    CARD16 temp;
    
    temp = data[1];
    temp <<= 8;
    temp += data[0];
    return temp;
}


/**
 * Read a little-endian, unaligned data value and return as 32-bit.
 */
static __inline__ CARD32 get_u32( const CARD8 * data )
{
    CARD32 temp;


    temp = data[3];
    temp <<= 8;
    temp += data[2];
    temp <<= 8;
    temp += data[1];
    temp <<= 8;
    temp += data[0];

    return temp;
}


/**
 * Parse version 0x01XX of the BIOS PInS structure.
 * 
 * Version 0x01XX of the BIOS PInS structure is only found in Millennium cards.
 *
 * \todo
 * There used to be an "OverclockMem" option that would scale the memory clock
 * by 12 instead 10.  Add support for this back in.
 */
static void mga_parse_bios_ver_1( struct mga_bios_values * bios, 
				  const CARD8 * bios_data )
{
    unsigned maxdac;

    if ( get_u16( & bios_data[24] ) ) {
	maxdac = get_u16( & bios_data[24] ) * 10;
    }
    else {
	/* There is some disagreement here between the Linux kernel matroxfb
	 * driver and the old X.org mga driver.  matroxfb has case-statements
	 * for 0 and 1 (with the values below), and the mga driver has
	 * case-statements for 1 and 2 (with the values below).  The default
	 * value for the mga driver is 17500, but the default value for the
	 * matroxfb driver is 240000.
	 */

	switch( bios_data[22] ) {
	case 0:  maxdac = 175000; break;
	case 1:  maxdac = 220000; break;
	case 2:  maxdac = 250000; break;
	default: maxdac = 240000; break;
	}
    }
    
    if ( get_u16( & bios_data[28] ) ) {
	bios->mem_clock = get_u16( & bios_data[28] ) * 10;
    }

    if ( (bios_data[48] & 0x01) == 0 ) {
	bios->fast_bitblt = TRUE;
    }

    bios->pixel.max_freq = maxdac;
}


/**
 * Parse version 0x02XX of the BIOS PInS structure.
 * 
 * Version 0x02XX of the BIOS PInS structure is only found in Millennium II
 * and Mystique cards.
 */
static void mga_parse_bios_ver_2( struct mga_bios_values * bios, 
				  const CARD8 * bios_data )
{
    if ( bios_data[41] != 0xff ) {
	const unsigned maxdac = (bios_data[41] + 100) * 1000;

	bios->pixel.max_freq = maxdac;
	bios->system.max_freq = maxdac;
    }

    if ( bios_data[43] != 0xff ) {
	const unsigned system_pll = (bios_data[43] + 100) * 1000;
	bios->mem_clock = system_pll;
    }
}


/**
 * Parse version 0x03XX of the BIOS PInS structure.
 * 
 * Version 0x03XX of the BIOS PInS structure is only found in G100 and G200
 * cards.
 */
static void mga_parse_bios_ver_3( struct mga_bios_values * bios, 
				  const CARD8 * bios_data )
{
    if ( bios_data[36] != 0xff ) {
	const unsigned maxdac = (bios_data[36] + 100) * 1000;

	bios->pixel.max_freq = maxdac;
	bios->system.max_freq = maxdac;
    }

    if ( (bios_data[52] & 0x20) != 0 ) {
	bios->pll_ref_freq = 14318;
    }
}


/**
 * Parse version 0x04XX of the BIOS PInS structure.
 * 
 * Version 0x04XX of the BIOS PInS structure is only found in G400 cards.
 */
static void mga_parse_bios_ver_4( struct mga_bios_values * bios, 
				  const CARD8 * bios_data )
{
    if ( bios_data[39] != 0xff ) {
	const unsigned maxdac = bios_data[39] * 4 * 1000;

	bios->pixel.max_freq = maxdac;
	bios->system.max_freq = maxdac;
    }

    if ( bios_data[38] != 0xff ) {
	const unsigned maxdac = bios_data[38] * 4 * 1000;

	bios->system.max_freq = maxdac;
    }

    if ( (bios_data[92] & 0x01) != 0 ) {
	bios->pll_ref_freq = 14318;
    }

    bios->host_interface = (bios_data[95] >> 3) & 0x07;

    if ( bios_data[65] != 0xff ) {
	const unsigned system_pll = bios_data[65] * 4 * 1000;
	bios->mem_clock = system_pll;
    }
}


/**
 * Parse version 0x05XX of the BIOS PInS structure.
 * 
 * Version 0x05XX of the BIOS PInS structure is only found in G450 and G550
 * cards.
 */
static void mga_parse_bios_ver_5( struct mga_bios_values * bios, 
				  const CARD8 * bios_data )
{
    const unsigned scale = (bios_data[4] != 0) ? 8000 : 6000;


    if ( bios_data[38] != 0xff ) {
	const unsigned maxdac = bios_data[38] * scale;

	bios->pixel.max_freq = maxdac;
	bios->system.max_freq = maxdac;
	bios->video.max_freq = maxdac;
    }

    if ( bios_data[36] != 0xff ) {
	const unsigned maxdac = bios_data[36] * scale;

	bios->system.max_freq = maxdac;
	bios->video.max_freq = maxdac;
    }

    if ( bios_data[37] != 0xff ) {
	const unsigned maxdac = bios_data[37] * scale;

	bios->video.max_freq = maxdac;
    }


    if ( bios_data[123] != 0xff ) {
	const unsigned mindac = bios_data[123] * scale;

	bios->pixel.min_freq = mindac;
	bios->system.min_freq = mindac;
	bios->video.min_freq = mindac;
    }

    if ( bios_data[121] != 0xff ) {
	const unsigned mindac = bios_data[121] * scale;

	bios->system.min_freq = mindac;
	bios->video.min_freq = mindac;
    }

    if ( bios_data[122] != 0xff ) {
	const unsigned mindac = bios_data[122] * scale;

	bios->video.min_freq = mindac;
    }


    if ( bios_data[92] != 0xff ) {
	const unsigned system_pll = bios_data[92] * 4 * 1000;
	bios->mem_clock = system_pll;
    }

    if ( (bios_data[110] & 0x01) != 0 ) {
	bios->pll_ref_freq = 14318;
    }

    bios->host_interface = (bios_data[113] >> 3) & 0x07;
}


/**
 * Read the BIOS data from the card and initialize internal values.
 */

Bool mga_read_and_process_bios( ScrnInfoPtr pScrn )
{
    CARD8  bios_data[0x20000];
    unsigned offset;
    MGAPtr pMga = MGAPTR(pScrn);
#ifndef XSERVER_LIBPCIACCESS
    Bool pciBIOS = TRUE;
#endif
    static const unsigned expected_length[] = { 0, 64, 64, 64, 128, 128 };
    unsigned version;
    unsigned pins_len;
    const CARD8 * pins_data;
    int err;
#ifdef BIOS_DEBUG
    static const char * const host_interface_strings[8] = {
	"Reserved",
	"Reserved",
	"Reserved",
	"Hybrid (AGP 4x on data transfers only)",
	"PCI",
	"AGP 1x",
	"AGP 2x",
	"AGP 4x"
    };
#endif


    /* Initialize the stored BIOS data to some reasonable values for the
     * card at hand.  This is done now so that even if the PInS data block
     * isn't found or can't be read we'll still have some reasonable values
     * to use.
     */
    if (pMga->chip_attribs)
	(void) memcpy(& pMga->bios, & pMga->chip_attribs->default_bios_values,
                      sizeof(struct mga_bios_values));


    /* If the BIOS address was probed, it was found from the PCI config space
     * If it was given in the config file, try to guess when it looks like it
     * might be controlled by the PCI config space.
     */

#ifdef XSERVER_LIBPCIACCESS
    err = pci_device_read_rom(pMga->PciInfo, bios_data);
#else
    if (pMga->BiosFrom == X_DEFAULT) {
	pciBIOS = FALSE;
    }
    else if (pMga->BiosFrom == X_CONFIG && pMga->BiosAddress < 0x100000) {
	pciBIOS = TRUE;
    }

    if (pciBIOS) {
	rlen = xf86ReadPciBIOS(0, pMga->PciTag, pMga->framebuffer_bar,
			       bios_data, sizeof(bios_data));
    }
    else {
	rlen = xf86ReadDomainMemory(pMga->PciTag, pMga->BiosAddress,
				    sizeof(bios_data), bios_data);
    }

    err = rlen < (bios_data[2] << 9);
#endif

    if (err) {
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		   "Could not retrieve video BIOS!\n");
	return FALSE;
    }
        
    /* Get the output mode set by the BIOS */
    pMga->BiosOutputMode = bios_data[0x7ff1];

    /* Get the video BIOS info block */
    if (strncmp((char *)(&bios_data[45]), "MATROX", 6)) {
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		   "Video BIOS info block not detected!\n");
	return FALSE;
    }

#if X_BYTE_ORDER == X_BIG_ENDIAN
    /* The offset information that is included in the BIOS on PC Matrox cards
     * is not there on PowerPC cards.  Instead, we have to search the BIOS
     * image for the magic signature.  This is the 16-bit value 0x412d.  This
     * value is followed by the length of the PInS block.  We know that this
     * must (currently) be either 0x80 or 0x40.
     * 
     * Happy hunting.
     */
    for (offset = 0 ; offset < 0x7ffc ; offset++) {
	if ((bios_data[offset] == 0x2e) && (bios_data[offset+1] == 0x41)
	    && ((bios_data[offset+2] == 0x80) || (bios_data[offset+2] == 0x40))) {
	    break;
	}
    }

    if (offset == 0x7ffc) {
	xf86DrvMsg(pScrn->scrnIndex, X_WARNING,
		   "Video BIOS PInS data not found!\n");
	return FALSE;
    }
#else
    /* Get the info block offset */
    offset = (unsigned)((bios_data[0x7ffd] << 8) | bios_data[0x7ffc]);
#endif

    /* Let the world know what we are up to */
    xf86DrvMsg(pScrn->scrnIndex, X_PROBED,
	       "Video BIOS info block at offset 0x%05lX\n",
	       (long)(offset));

    
    /* Determine the version of the PInS block.  This will determine how the
     * data is processed.  Only the first version of the PInS data structure
     * did *NOT* have the initial 0x412e (in little-endian order!) signature.
     */
    
    pins_data = & bios_data[ offset ];
    if ( (pins_data[0] == 0x2e) && (pins_data[1] == 0x41) ) {
	version = pins_data[5];
	pins_len = pins_data[2];
    }
    else {
	version = 1;
	pins_len = get_u16( pins_data );
    }
    
    
    if ( (version < 1) || (version > 5) ) {
	xf86DrvMsg(pScrn->scrnIndex, X_INFO,
		   "PInS data version (%u) not supported.\n", version);
	return FALSE;
    }

    if ( pins_len != expected_length[ version ] ) {
	xf86DrvMsg(pScrn->scrnIndex, X_INFO,
		   "PInS data length (%u) does not match expected length (%u)"
		   " for version %u.X.\n",
		   pins_len, expected_length[ version ], version);
	return FALSE;
    }
    
    switch( version ) {
    case 1:  mga_parse_bios_ver_1( & pMga->bios, pins_data ); break;
    case 2:  mga_parse_bios_ver_2( & pMga->bios, pins_data ); break;
    case 3:  mga_parse_bios_ver_3( & pMga->bios, pins_data ); break;
    case 4:  mga_parse_bios_ver_4( & pMga->bios, pins_data ); break;
    case 5:  mga_parse_bios_ver_5( & pMga->bios, pins_data ); break;
    }
    
#ifdef BIOS_DEBUG
    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
	       "system VCO = [%u, %u]\n",
	       pMga->bios.system.min_freq, pMga->bios.system.max_freq);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
	       "pixel VCO  = [%u, %u]\n",
	       pMga->bios.pixel.min_freq,  pMga->bios.pixel.max_freq);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
	       "video VCO  = [%u, %u]\n",
	       pMga->bios.video.min_freq,  pMga->bios.video.max_freq);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
	       "memory clock = %ukHz\n", pMga->bios.mem_clock);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
	       "PLL reference frequency = %ukHz\n",
	       pMga->bios.pll_ref_freq);
    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
	       "%s fast bitblt\n",
	       (pMga->bios.fast_bitblt) ? "Has" : "Does not have");
    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
	       "Host interface: %s (%u)\n",
	       host_interface_strings[ pMga->bios.host_interface ],
	       pMga->bios.host_interface);
#endif

    return TRUE;
}