// Simple utility to switch a Savage board between CRT/LCD devices. // T. N. Roberts, 99-Aug-26. // Ported to FreeBSD/i386 by Bruce M Simpson #if defined(__FreeBSD__) #include #include #include #include #include #include #define OUTB(val,port) outb(port,val) #define INB(port) inb(port) #define IOPERM(x,y,z) i386_set_ioperm(x,y,z) typedef struct vm86_intcall_args INTCALL_ARGS_T; #define EAX vmf.eax.r_ex #define EBX vmf.ebx.r_ex #define ECX vmf.ecx.r_ex #define EDX vmf.edx.r_ex #define INTCALL_INIT() static __inline INTCALL(int intnum, INTCALL_ARGS_T *args) { args->intnum = intnum; return(!i386_vm86(VM86_INTCALL, args)); } #else /* Linux */ #include #define extern #include #undef extern #include "lrmi.h" #define OUTB(val,port) outb(val,port) #define INB(port) inb(port) #define IOPERM(x,y,z) ioperm(x,y,z) typedef struct LRMI_regs INTCALL_ARGS_T; #define EAX eax #define EBX ebx #define ECX ecx #define EDX edx #define INTCALL_INIT() \ do { \ if (!LRMI_init()) \ return (1); \ } while (0) static __inline INTCALL(int intnum, INTCALL_ARGS_T *args) { return(LRMI_int(intnum, args)); } #endif /* Linux */ // Usage: // s3switch [-q] [crt|lcd|both] // Define the Savage chip classes. PCI id's stolen from xf86PciInfo.h #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_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 supported on Savage3D only."); 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( 0x61, 1, enable ); IOPERM( 0x80, 1, enable ); IOPERM( 0x3b0, 0x30, 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_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: %04x\n", gPCIid ); exit(-1); } if( ChipClass == S3_SAVAGEMXIX ) { OUTB( 0xc0, 0x3d4 ); jTvFormat = INB( 0x3d5 ); 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 ); //printf( "Device ID: %04x\n", gPCIid); // 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_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 ) { INTCALL_ARGS_T sa; int iResult = 0; INTCALL_INIT(); /* Go set the active device. */ memset(&sa, 0, sizeof(sa)); sa.EAX = 0x4F14; sa.EBX = 0x0003; sa.ECX = iDevice; if( ChipClass == S3_SAVAGEMXIX ) sa.ECX |= DUO_ON; iResult = INTCALL(0x10, &sa); if( !iResult ) { fprintf( stderr, "Could not set device (vm86 failure)\n" ); return 1; } if ( (sa.EAX & 0xffff) != 0x4f ) { fprintf( stderr, "BIOS returned error code.\n" ); return 1; } return 0; } unsigned short set_tv_state( int state ) { INTCALL_ARGS_T sa; int iResult = 0; INTCALL_INIT(); /* And go set the TV state. */ memset(&sa, 0, sizeof(sa)); sa.EAX = 0x4F14; sa.EBX = 0x0007; sa.ECX = state; sa.EDX = TV_FORMAT_MASK; iResult = INTCALL(0x10, &sa); if( !iResult ) { fprintf( stderr, "Could not set TV state (vm86 failure)\n" ); return 1; } if ( (sa.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( 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( 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; } int main( int argc, char ** argv ) { int quiet = 0; int newstate = 0; int newtv = 0; if( geteuid() != 0 ) { fprintf( stderr, "s3switch must be setuid root.\n" ); exit( -1 ); } // 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( strcmp( *argv, "-h" ) == 0 ) { usage(); exit( 0 ); } else { fprintf( stderr, "Unknown argument: %s\n", *argv ); usage(); exit( -1 ); } } IOAccess( 1 ); fetch_bios_data(); if( newtv ) set_new_tvstate( newtv ); if( newstate ) set_new_state( newstate ); if( !quiet ) print_current_state( ); IOAccess( 0 ); return 0; }