diff options
Diffstat (limited to 'src/mga_bios.c')
-rw-r--r-- | src/mga_bios.c | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/src/mga_bios.c b/src/mga_bios.c new file mode 100644 index 0000000..c2aacbc --- /dev/null +++ b/src/mga_bios.c @@ -0,0 +1,521 @@ +/* + * (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> + */ + +/* All drivers should typically include these */ +#include "xf86.h" +#include "xf86_OSproc.h" +#include "xf86Resources.h" + +/* All drivers need this */ +#include "xf86_ansic.h" + +#include "compiler.h" + +/* Drivers for PCI hardware need this */ +#include "xf86PciInfo.h" + +/* Drivers that need to access the PCI config space directly need this */ +#include "xf86Pci.h" + +#include "Xmd.h" + +#include "mga.h" + + +/** + * 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; +} + + +/** + * Initialize reasonable defaults for values that normally come form the BIOS. + * + * For each generation of hardware, provide reasonable default values, based + * on the type of hardware and chipset revision, for various values that are + * normally read from the PInS structure in the BIOS. This provides a backup + * in case the PInS structure cannot be found. + * + * \param pMga Pointer to the MGA-private data. + * \param bios Pointer to the structure that holds values read from the BIOS. + * + * \todo + * Determine if different default values should be used for G400 and G450 + * cards. These cards have the same PCI ID, but can be identified by a + * different chip revision. The G450 cards have a revision of 3 or higher. + */ + +static void mga_initialize_bios_values( MGAPtr pMga, + struct mga_bios_values * bios ) +{ + (void) memset( bios, 0, sizeof( *bios ) ); + + bios->pixel.min_freq = 50000; + + switch( pMga->Chipset ) { + case PCI_CHIP_MGA2064: + case PCI_CHIP_MGA2164: + case PCI_CHIP_MGA2164_AGP: + bios->pixel.max_freq = 220000; + + bios->pll_ref_freq = 14318; + bios->mem_clock = 50000; + break; + + case PCI_CHIP_MGA1064: + /* There used to be code in MGARamdacInit (mga_dacG.c) that would + * set this to 170000 if the chip revision was less than 3. Is that + * needed here? + */ + + bios->system.max_freq = 230000; + bios->pixel.max_freq = 230000; + + bios->pll_ref_freq = 14318; + bios->mem_clock = 50000; + break; + + case PCI_CHIP_MGAG100_PCI: + case PCI_CHIP_MGAG100: + case PCI_CHIP_MGAG200_PCI: + case PCI_CHIP_MGAG200: + bios->system.max_freq = 230000; + bios->pixel.max_freq = 230000; + + bios->system.min_freq = 50000; + + bios->pll_ref_freq = 27050; + bios->mem_clock = 50000; + break; + + case PCI_CHIP_MGAG400: + bios->system.max_freq = 252000; + bios->pixel.max_freq = 252000; + + bios->system.min_freq = 50000; + + bios->pll_ref_freq = 27050; + bios->mem_clock = 200000; + break; + + case PCI_CHIP_MGAG550: + bios->system.min_freq = 256000; + bios->pixel.min_freq = 256000; + bios->video.min_freq = 256000; + bios->system.max_freq = 600000; + bios->pixel.max_freq = 600000; + bios->video.max_freq = 600000; + + bios->pll_ref_freq = 27050; + bios->mem_clock = 284000; + break; + } +} + + +/** + * Parse version 0x01XX of the BIOS PInS structure. + * + * Version 0x01XX of the BIOS PInS structure is only found in Millenium 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 Millenium 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] & 0x01) != 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; + } + + 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; + } +} + + +/** + * Read the BIOS data from the card and initialize internal values. + */ + +Bool mga_read_and_process_bios( ScrnInfoPtr pScrn ) +{ + CARD8 bios_data[0x10000]; + unsigned offset; + MGAPtr pMga = MGAPTR(pScrn); + Bool pciBIOS = TRUE; + int rlen; + static const unsigned expected_length[] = { 0, 64, 64, 64, 128, 128 }; + unsigned version; + unsigned pins_len; + const CARD8 * pins_data; + + + /* 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. + */ + + mga_initialize_bios_values( pMga, & pMga->bios ); + + + /* 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. + */ + + 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->FbBaseReg, + bios_data, sizeof(bios_data)); + } + else { + rlen = xf86ReadDomainMemory(pMga->PciTag, pMga->BiosAddress, + sizeof(bios_data), bios_data); + } + + if (rlen < (bios_data[2] << 9)) { + 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 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"); +#endif + + return TRUE; +} |