[ltp] success with simultaneous CRT/LCD display on T21.

D. Sen linux-thinkpad@www.bm-soft.com
Tue, 10 Jul 2001 14:26:39 -0400


This is a multi-part message in MIME format.
--------------54F8AFA65C969D6F3BB748C5
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

I managed to get simultaneous CRT + LCD display working on the T21.
I have included the source to the modified s3switch utility that will
let you get both the CRT + LCD display up. I have only changed one line
from the Tim Roberts' original code
(.http://www.probo.com/timr/savage40.html)  The lrmi package from
http://sourceforge.net/projects/lrmi/ is needed to compile the code.

I also had to delete the line "       Option "LCDClock"  ......" from my
/etc/X11/XF86Config-4 to get a correct display from both the LCD and
the CRT

Six months after buying the T21, I can use it to make presentations! No
thanks to S3 or IBM for providing any sort of hint/information on how
this problem could have been solved. I am glad Tim made the code
available so I could tinker with it ......albeit blindly :-)

DS

--
D. Sen, Room E167
AT&T Labs-Research
Shannon Laboratory
180 Park Ave.
Florham Park NJ 07932-0971
Ph: 973-360-8546
http://www.research.att.com/~dsen


--------------54F8AFA65C969D6F3BB748C5
Content-Type: text/plain; charset=us-ascii;
 name="s3switch.c"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="s3switch.c"

// Simple utility to switch a Savage board between CRT/LCD devices.
// T. N. Roberts, 99-Aug-26.

// D. Sen, 2001-Jul-10 (Modified to include "duo_on" for T21s with 8c12 chipsets"
// Linux x86 only.

#include <stdio.h>
#define extern
#include <asm/io.h>
#undef extern

#include "lrmi.h"

// 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

enum {
  S3_SAVAGE3D,
  S3_SAVAGE4,
  S3_SAVAGEMXIX,
  S3_SAVAGE2000,
  S3_PROSAVAGE
} 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:
	    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;
	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, jDevices=%x, cr79=%x\n", gPCIid, jDevices, cr79);

    // The Savage4 and Savage2000 are the only chips which actually detect
    // the presence of the devices.  For the others, we just have to assume.

    if( ChipClass == S3_SAVAGE3D )
    {
	jDevices = (jDevices & 0x0f) | CRT_ATTACHED | TV_ATTACHED;
    }
    if( ChipClass == S3_SAVAGEMXIX )
    {
	jDevices = (jDevices & 0x0f) | CRT_ATTACHED | TV_ATTACHED | LCD_ATTACHED;
    }

}


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|0x80; // dsen added |0x80 for DUO_ON on 20010629

    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( 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;
}

--------------54F8AFA65C969D6F3BB748C5--


----- The Linux ThinkPad mailing list -----
The linux-thinkpad mailing list home page is at:
http://www.bm-soft.com/~bm/tp_mailing.html