/* Simple utility to switch a Savage board between CRT/LCD devices. T. N. Roberts, 99-Aug-26. */ #include #include #include #include #include #define extern #include #undef extern #include "libx86.h" #define S3SWITCH_VERSION_STRING "0.1" /* Define the Savage chip classes. PCI IDs taken from xorg driver */ #define PCI_CHIP_VIRGEGX2 0x8A10 #define PCI_CHIP_SAVAGE3D 0x8A20 #define PCI_CHIP_SAVAGE3D_MV 0x8A21 #define PCI_CHIP_SAVAGE4 0x8A22 #define PCI_CHIP_SAVAGE2000 0x9102 #define PCI_CHIP_PROSAVAGE_PM 0x8A25 #define PCI_CHIP_PROSAVAGE_KM 0x8A26 #define PCI_CHIP_SAVAGE_MX_MV 0x8c10 #define PCI_CHIP_SAVAGE_MX 0x8c11 #define PCI_CHIP_SAVAGE_IX_MV 0x8c12 #define PCI_CHIP_SAVAGE_IX 0x8c13 #define PCI_CHIP_TWISTERP 0x8d01 #define PCI_CHIP_TWISTERK 0x8d02 #define PCI_CHIP_PROSAVAGE_DDR 0x8d03 #define PCI_CHIP_PROSAVAGE_DDRK 0x8d04 #define PCI_CHIP_SUPSAV_MX128 0x8c22 #define PCI_CHIP_SUPSAV_MX64 0x8c24 #define PCI_CHIP_SUPSAV_MX64C 0x8c26 #define PCI_CHIP_SUPSAV_IX128SDR 0x8c2a #define PCI_CHIP_SUPSAV_IX128DDR 0x8c2b #define PCI_CHIP_SUPSAV_IX64SDR 0x8c2c #define PCI_CHIP_SUPSAV_IX64DDR 0x8c2d #define PCI_CHIP_SUPSAV_IXCSDR 0x8c2e #define PCI_CHIP_SUPSAV_IXCDDR 0x8c2f enum { S3_VIRGEGX2, S3_SAVAGE3D, S3_SAVAGE4, S3_SAVAGEMXIX, S3_SAVAGE2000, S3_PROSAVAGE, S3_SUPERSAVAGE } ChipClass; /* Define the device attachment bits. This is CR6D on the non-mobile chips, and CR6B on the mobiles. Savage3D does not support LCD, and the Savage4 does not support TV. */ #define CRT_ACTIVE 0x01 #define LCD_ACTIVE 0x02 #define TV_ACTIVE 0x04 #define CRT_ATTACHED 0x10 #define LCD_ATTACHED 0x20 #define TV_ATTACHED 0x40 #define DUO_ON 0x80 static char * devices[] = { " CRT", " LCD", " TV" }; /* Define the TV format bits in CR6B (non-mobile) or CRC0 (mobile). */ #define TV_FORMAT_MASK 0x0c #define TV_FORMAT_NTSCJ 0x00 #define TV_FORMAT_NTSC 0x04 #define TV_FORMAT_PAL 0x08 /* Global state: */ unsigned int gPCIid = 0; unsigned char jTvFormat = 0; unsigned char jDevices = 0; unsigned char cr79 = 0; void usage() { puts( "Usage: s3switch [-q] [crt|lcd|both|tv] [ntsc|ntscj|pal]" ); puts( " -q requests quiet operation." ); puts( " crt, lcd and tv activates output to those devices. Several devices may be" ); puts( " specified. Only devices which are actually attached may be activated." ); puts( " both is a shortcut for 'crt lcd'." ); puts( " ntscj, ntsc and pal specify the video format for TV output." ); puts( " This is not supported on Savage4."); puts( " With no parameters, displays all devices currently attached and active."); } void IOAccess( int enable ) { /* Allow or disallow access to I/O ports. */ ioperm( 0x40, 4, enable ); ioperm( 0x4f, 1, enable ); ioperm( 0x61, 1, enable ); ioperm( 0x70, 1, enable ); ioperm( 0x71, 1, enable ); ioperm( 0x80, 1, enable ); ioperm( 0xb2, 1, enable ); ioperm( 0x3b0, 0x30, enable ); ioperm( 0xeb, 1, enable ); } void fetch_bios_data() { /* Figure out what kind of Savage it is. */ outb( 0x2d, 0x3d4 ); gPCIid = inb( 0x3d5 ) << 8; outb( 0x2e, 0x3d4 ); gPCIid |= inb( 0x3d5 ); switch( gPCIid ) { case PCI_CHIP_VIRGEGX2: ChipClass = S3_VIRGEGX2; break; case PCI_CHIP_SAVAGE3D: case PCI_CHIP_SAVAGE3D_MV: ChipClass = S3_SAVAGE3D; break; case PCI_CHIP_SAVAGE4: ChipClass = S3_SAVAGE4; break; case PCI_CHIP_SAVAGE2000: ChipClass = S3_SAVAGE2000; break; case PCI_CHIP_PROSAVAGE_PM: case PCI_CHIP_PROSAVAGE_KM: case PCI_CHIP_TWISTERP: case PCI_CHIP_TWISTERK: case PCI_CHIP_PROSAVAGE_DDR: case PCI_CHIP_PROSAVAGE_DDRK: ChipClass = S3_PROSAVAGE; break; case PCI_CHIP_SAVAGE_MX_MV: case PCI_CHIP_SAVAGE_MX: case PCI_CHIP_SAVAGE_IX_MV: case PCI_CHIP_SAVAGE_IX: ChipClass = S3_SAVAGEMXIX; break; case PCI_CHIP_SUPSAV_MX128: case PCI_CHIP_SUPSAV_MX64: case PCI_CHIP_SUPSAV_MX64C: case PCI_CHIP_SUPSAV_IX128SDR: case PCI_CHIP_SUPSAV_IX128DDR: case PCI_CHIP_SUPSAV_IX64SDR: case PCI_CHIP_SUPSAV_IX64DDR: case PCI_CHIP_SUPSAV_IXCSDR: case PCI_CHIP_SUPSAV_IXCDDR: ChipClass = S3_SUPERSAVAGE; break; default: printf( "PCI id is not a recognized Savage or Virge GX2: %04x\n", gPCIid ); exit(-1); } if( ChipClass == S3_SAVAGEMXIX ) { outb( 0xc0, 0x3d4 ); jTvFormat = inb( 0x3d5 ); outb( 0x6b, 0x3d4 ); jDevices = inb( 0x3d5 ); } else if( ChipClass == S3_PROSAVAGE ) { outb( 0x5f, 0x3c4 ); jTvFormat = inb( 0x3c5 ); outb( 0x6b, 0x3d4 ); jDevices = inb( 0x3d5 ); } else { outb( 0x6b, 0x3d4 ); jTvFormat = inb( 0x3d5 ); outb( 0x6d, 0x3d4 ); jDevices = inb( 0x3d5 ); } outb( 0x79, 0x3d4 ); cr79 = inb( 0x3d5 ); /* The Savage4 and Savage2000 are the only chips which actually detect the presence of the devices. For the others, we just have to assume. */ switch( ChipClass ) { case S3_VIRGEGX2: jDevices = (jDevices & 0x0f) | CRT_ATTACHED | TV_ATTACHED; break; case S3_SAVAGE3D: jDevices = (jDevices & 0x0f) | CRT_ATTACHED | TV_ATTACHED; break; case S3_SAVAGE4: case S3_SAVAGE2000: /* These two get it right. */ break; case S3_SAVAGEMXIX: case S3_PROSAVAGE: case S3_SUPERSAVAGE: default: jDevices = (jDevices & 0x0f) | CRT_ATTACHED | TV_ATTACHED | LCD_ATTACHED; break; } } unsigned short set_active_device( int iDevice ) { struct LRMI_regs r; int iResult = 0; if (!LRMI_init()) return 1; /* Go set the active device. */ memset( &r, 0, sizeof(r) ); r.eax = 0x4f14; /* S3 extended functions */ r.ebx = 0x0003; /* set active device */ r.ecx = iDevice; if( ChipClass == S3_SAVAGEMXIX ) r.ecx |= DUO_ON; iResult = LRMI_int( 0x10, &r ); if( !iResult ) { fprintf( stderr, "Could not set device (vm86 failure)\n" ); return 1; } if ( (r.eax & 0xffff) != 0x4f ) { fprintf( stderr, "BIOS returned error code.\n" ); return 1; } return 0; } unsigned short set_tv_state( int state ) { struct LRMI_regs r; int iResult = 0; if (!LRMI_init()) return 1; /* And go set the TV state. */ memset( &r, 0, sizeof(r) ); r.eax = 0x4f14; /* S3 extended functions */ r.ebx = 0x0007; /* set TV state */ r.ecx = state; r.edx = TV_FORMAT_MASK; iResult = LRMI_int( 0x10, &r ); if( !iResult ) { fprintf( stderr, "Could not set TV state (vm86 failure)\n" ); return 1; } if ( (r.eax & 0xffff) != 0x4f ) { fprintf( stderr, "BIOS returned error code.\n" ); return 1; } return 0; } void print_current_state() { int i; printf( "Devices attached: " ); if( !(jDevices & 0x70) ) { /* How can this be? */ printf( "none" ); } else for( i = 0; i < 3; i++ ) if( jDevices & (0x10 << i) ) printf( "%s", devices[i] ); printf( "\nDevices active: " ); if( !(jDevices & 0x07) ) { /* How can this be? */ printf( "none\n" ); } else for( i = 0; i < 3; i++ ) if( jDevices & (0x01 << i) ) printf( "%s", devices[i] ); if( jDevices & TV_ATTACHED ) { static char * szTV[] = { "NTSC-J", "NTSC", "PAL" }; printf( "\nCurrent TV format is %s", szTV[(jTvFormat & TV_FORMAT_MASK) >> 2] ); } printf( "\n" ); } void set_new_state( int newstate ) { /* We should prohibit TV on Savage4. */ if( ((jDevices >> 4) & newstate) != newstate ) { fprintf( stderr, "You attempted to activate a device which is not connected.\n" ); /* Alternatively, quiet = 0, return. */ print_current_state(); exit( -2 ); } set_active_device( newstate ); /* If the LCD state changed, we need to adjust cr79 in Savage4. These values are somewhat magical, and are set by the X server. */ if( (ChipClass == S3_SAVAGE4) || (ChipClass == S3_SAVAGE2000) ) { if( (jDevices & LCD_ACTIVE) && !(newstate & LCD_ACTIVE) ) { /* The LCD was alive and now it isn't. We can increase cr79. */ if( (cr79 == 5) || (cr79 == 8) ) { cr79 = (cr79 == 5) ? 8 : 0x0e; ioperm( 0x3d4, 2, 1 ); outw( (cr79 << 8) | 0x79, 0x3d4 ); ioperm( 0x3d4, 2, 0 ); } } else if( !(jDevices & LCD_ACTIVE) && (newstate & LCD_ACTIVE) ) { /* The LCD was off and now it's on. We must cut back cr79. */ if( (cr79 == 8) || (cr79 == 0xe) ) { cr79 = (cr79 == 8) ? 5 : 8; ioperm( 0x3d4, 2, 1 ); outw( (cr79 << 8) | 0x79, 0x3d4 ); ioperm( 0x3d4, 2, 0 ); } } } fetch_bios_data(); return; } void set_new_tvstate( int tvstate ) { if( ChipClass == S3_SAVAGE4 ) return; set_tv_state( tvstate ); fetch_bios_data(); return; } void dump_regs() { int i; /* Print out sequential registers */ printf( "\nSR x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF" ); for ( i = 0; i < 0x70; i++ ) { if ( !(i % 16) ) printf( "\nSR%xx ", i >> 4 ); outb( i, 0x3c4 ); printf( " %02x", inb( 0x3c5 ) ); } printf( "\n" ); /* Print out CRT controller registers */ printf( "\nCR x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF" ); for ( i = 0; i < 0xb7; i++ ) { if ( !(i % 16) ) printf( "\nCR%xx ", i >> 4 ); outb( i, 0x3d4 ); printf( " %02x", inb( 0x3d5 ) ); } printf( "\n" ); } int main( int argc, char ** argv ) { int quiet = 0; int newstate = 0; int newtv = -1; int regdump = 0; /* Scan through the argument list. We do very primitive checking here. */ while( *++argv ) { if( strcmp( *argv, "-q" ) == 0 ) quiet++; else if( strcasecmp( *argv, "crt" ) == 0 ) newstate |= CRT_ACTIVE; else if( strcasecmp( *argv, "lcd" ) == 0 ) newstate |= LCD_ACTIVE; else if( strcasecmp( *argv, "both" ) == 0 ) newstate |= CRT_ACTIVE | LCD_ACTIVE; else if( strcasecmp( *argv, "tv" ) == 0 ) newstate |= TV_ACTIVE; else if( strcasecmp( *argv, "ntsc-j" ) == 0 ) newtv = TV_FORMAT_NTSCJ; else if( strcasecmp( *argv, "ntscj" ) == 0 ) newtv = TV_FORMAT_NTSCJ; else if( strcasecmp( *argv, "ntsc" ) == 0 ) newtv = TV_FORMAT_NTSC; else if( strcasecmp( *argv, "pal" ) == 0 ) newtv = TV_FORMAT_PAL; else if( strcasecmp( *argv, "regdump" ) == 0) regdump = 1; else if( strcmp( *argv, "-V" ) == 0 ) { printf("s3switch " S3SWITCH_VERSION_STRING "\n"); exit( 0 ); } else if( strcmp( *argv, "-h" ) == 0 ) { usage(); exit( 0 ); } else { fprintf( stderr, "Error: Unknown argument: %s\n", *argv ); usage(); exit( -1 ); } } if( geteuid() != 0 ) { fprintf( stderr, "Error: s3switch must be run as root.\n" ); exit( -1 ); } IOAccess( 1 ); fetch_bios_data(); if( newtv != -1 ) set_new_tvstate( newtv ); if( newstate ) set_new_state( newstate ); if( !quiet ) print_current_state( ); if( regdump ) dump_regs( ); IOAccess( 0 ); return 0; }