/* 	Copyright (c) 1992 NeXT Computer, Inc.  All rights reserved. 
 *
 *  CirrusLogicGD542X.m
 *
 */


#import <driverkit/i386/IOEISADeviceDescription.h>
#import "CirrusLogicGD542X.h"
#import <kernserv/kern_server_types.h>
#import <driverkit/i386/displayRegisters.h>
#import <stdio.h>
#import <string.h>

#define	MIN(a,b) (((a)<(b))?(a):(b))


//
// typedef used to store arrays of register index and values.
// -1 as index indicates end of the set...
//
typedef struct _SVGAIndexValuePair {
    signed short index;
    unsigned char value;
} SVGAIndexValuePair;


//
// typedef defining a given graphics mode.  A graphics mode
// consists of all the register/value pairs that, when set,
// completely define a mode of operation.
//
typedef struct _CirrusLogicGD542XMode {
    const char *name;			/* human-readable mode name */
    const SVGAIndexValuePair *generalRegisters;
    const SVGAIndexValuePair *sequencerRegisters;
    const SVGAIndexValuePair *crtControllerRegisters;
    const SVGAIndexValuePair *graphicsControllerRegisters;
    const SVGAIndexValuePair *attributeControllerRegisters;
} CirrusLogicGD542XMode;


/***************************************
 * CirrusLogicGD542X_1024x768x2x60hz mode
 ***************************************/

static const SVGAIndexValuePair
CirrusLogicGD542X_1024x768x2x60hz_generalRegisters[] = {
    {0, 0x3F},
    {-1, 0}
};

static const SVGAIndexValuePair
CirrusLogicGD542X_1024x768x2x60hz_sequencerRegisters[] = {
    {0, 0x03}, {1, 0x01}, {2, 0x0F}, {3, 0x00}, {4, 0x06},
    {-1, 0}
};

static const SVGAIndexValuePair
CirrusLogicGD542X_1024x768x2x60hz_crtControllerRegisters[] = {
    {0x11,0x00},	// Turn off lock allowing changing 0..7
    {0x00,0xA1}, {0x01,0x7F}, {0x02,0x80}, {0x03,0x04}, {0x04,0x88},
    {0x05,0x9E}, {0x06,0x26}, {0x07,0xFD}, {0x08,0x00}, {0x09,0x60},
    {0x0A,0x00}, {0x0B,0x00}, {0x0C,0x00}, {0x0D,0x00}, {0x0E,0x00},
    {0x0F,0x00}, {0x10,0x08}, {0x11,0x8A}, {0x12,0xFF}, {0x13,0x40},
    {0x14,0x00}, {0x15,0x04}, {0x16,0x22}, {0x17,0xC3}, {0x18,0xFF},
    {0x1b,0x02},
    {-1, 0}
};

static const SVGAIndexValuePair
CirrusLogicGD542X_1024x768x2x60hz_graphicsControllerRegisters[] = {
    {0x00,0x00}, {0x01,0x00}, {0x02,0x00}, {0x03,0x00}, {0x04,0x00},
    {0x05,0x00}, {0x06,0x05}, {0x07,0x0F}, {0x08,0xFF},
    {-1, 0}
};

static const SVGAIndexValuePair
CirrusLogicGD542X_1024x768x2x60hz_attributeControllerRegisters[] = {
    {0x00,0x00}, {0x01,0x01}, {0x02,0x02}, {0x03,0x03}, {0x04,0x04},
    {0x05,0x05}, {0x06,0x14}, {0x07,0x07}, {0x08,0x38}, {0x09,0x39},
    {0x0A,0x3A}, {0x0B,0x3B}, {0x0C,0x3C}, {0x0D,0x3D}, {0x0E,0x3E},
    {0x0F,0x3F}, {0x10,0x01}, {0x11,0x00}, {0x12,0x0F}, {0x13,0x00},
    {0x14,0x00},
    {-1, 0}
};


static const CirrusLogicGD542XMode
CirrusLogicGD542X_1024x768x2x60hz = {
    "1024x768x2x60hz",
    CirrusLogicGD542X_1024x768x2x60hz_generalRegisters,
    CirrusLogicGD542X_1024x768x2x60hz_sequencerRegisters,
    CirrusLogicGD542X_1024x768x2x60hz_crtControllerRegisters,
    CirrusLogicGD542X_1024x768x2x60hz_graphicsControllerRegisters,
    CirrusLogicGD542X_1024x768x2x60hz_attributeControllerRegisters
};


/**************************************
 * Framebuffer characteristics.
 **************************************/

#define FRAMEBUFFER_ADDRESS ((void *) 0xa0000)

static const IODisplayInfo modeTable[] = {
    {
	//
	// CirrusLogicGD542X 1024 x 768 x 2 x 60hz
	//
	1024, 768, 1024, 
	
	//
	// rowbytes =
	//	#bytes/scanline =
	//	((pixels/line) * (2 bits/pixel) * (byte/8 bits)) =
	//	(pixel width / 4)
	//
	256, 60, 0, IO_2BitsPerPixel, IO_OneIsBlackColorSpace,
	"WW", 0, (void *)&CirrusLogicGD542X_1024x768x2x60hz
    }
    /* Add more modes here. */
};
#define modeTableCount  (sizeof(modeTable) / sizeof(IODisplayInfo))


@implementation CirrusLogicGD542X


//
// BEGIN:	Implementation of private routines for SVGA
//


static void SetBrightness(unsigned int level)
// Description:	Sets the screen's brightness.  This implementation
//		uses a fixed gamma value.  It sets the palette
//		values according to the brightness level.
{
    unsigned char val;

    val = EV_SCALE_BRIGHTNESS(level, WHITE_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_AWMR, (unsigned char)WHITE_INDEX);
    outb(WRIT_COLR_PEL_DATA, val);
    outb(WRIT_COLR_PEL_DATA, val);
    outb(WRIT_COLR_PEL_DATA, val);

    val = EV_SCALE_BRIGHTNESS(level, LIGHT_GRAY_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_AWMR, (unsigned char)LIGHT_GRAY_INDEX);
    outb(WRIT_COLR_PEL_DATA, val);
    outb(WRIT_COLR_PEL_DATA, val);
    outb(WRIT_COLR_PEL_DATA, val);

    val = EV_SCALE_BRIGHTNESS(level, DARK_GRAY_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_AWMR, (unsigned char)DARK_GRAY_INDEX);
    outb(WRIT_COLR_PEL_DATA, val);
    outb(WRIT_COLR_PEL_DATA, val);
    outb(WRIT_COLR_PEL_DATA, val);

    val = EV_SCALE_BRIGHTNESS(level, BLACK_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_AWMR, (unsigned char)BLACK_INDEX);
    outb(WRIT_COLR_PEL_DATA, val);
    outb(WRIT_COLR_PEL_DATA, val);
    outb(WRIT_COLR_PEL_DATA, val);
}


static void setColorMapToLinearMonochrome()
// Description:	Sets the color map to linear monochrome by zeroing
//		out the entire table, then setting the first four
//		palette values correctly.
{
    int i;

    for (i = 0; i < 256; i++) {
	outb(WRIT_COLR_PEL_AWMR, i);
	outb(WRIT_COLR_PEL_DATA, 0x00);
	outb(WRIT_COLR_PEL_DATA, 0x00);
	outb(WRIT_COLR_PEL_DATA, 0x00);
    }
    outb(WRIT_COLR_PEL_AWMR, WHITE_INDEX);
    outb(WRIT_COLR_PEL_DATA, WHITE_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_DATA, WHITE_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_DATA, WHITE_PALETTE_VALUE);
    
    outb(WRIT_COLR_PEL_AWMR, LIGHT_GRAY_INDEX);
    outb(WRIT_COLR_PEL_DATA, LIGHT_GRAY_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_DATA, LIGHT_GRAY_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_DATA, LIGHT_GRAY_PALETTE_VALUE);
    
    outb(WRIT_COLR_PEL_AWMR, DARK_GRAY_INDEX);
    outb(WRIT_COLR_PEL_DATA, DARK_GRAY_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_DATA, DARK_GRAY_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_DATA, DARK_GRAY_PALETTE_VALUE);
    
    outb(WRIT_COLR_PEL_AWMR, BLACK_INDEX);
    outb(WRIT_COLR_PEL_DATA, BLACK_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_DATA, BLACK_PALETTE_VALUE);
    outb(WRIT_COLR_PEL_DATA, BLACK_PALETTE_VALUE);
}


- (void) _selectMode
// Description:	During initialization, this selects the configured mode
//		and sets the display info accordingly.
{
    selectedMode = [self selectMode:modeTable count:modeTableCount valid:NULL];

    if (selectedMode < 0) {
	IOLog("%s: Sorry, cannot use requested display mode.\n", [self name]);
	selectedMode = 0;
    }

    *[self displayInfo] = modeTable[selectedMode];
}


- (void)_SVGASetGeneralRegistersForMode:
	(const CirrusLogicGD542XMode *)cirrusLogicGD542XMode
// Description:	Set all the general registers for the given mode.
{
    const SVGAIndexValuePair *regInfo;

    regInfo = cirrusLogicGD542XMode->generalRegisters;
    while (regInfo->index != -1) {
	outb(WRIT_EIDR_GEN_MISC_OP, regInfo->value);
	regInfo++;
    }
}


- (void)_SVGASetSequencerRegistersForMode:
	(const CirrusLogicGD542XMode *)cirrusLogicGD542XMode
// Description:	Set all the sequencer registers for the given mode.
{
    const SVGAIndexValuePair *regInfo;
    
    regInfo = cirrusLogicGD542XMode->sequencerRegisters;
    while (regInfo->index != -1) {
	IOWriteRegister(EIDR_SEQ_ADDR, (char)(regInfo->index), regInfo->value);
	regInfo++;
    }
}


- (void)_SVGASetCrtControllerRegistersForMode:
	(const CirrusLogicGD542XMode *)cirrusLogicGD542XMode
// Description:	Set all the crt controller registers for the given mode.
{
    const SVGAIndexValuePair *regInfo;
    
    regInfo = cirrusLogicGD542XMode->crtControllerRegisters;
    while (regInfo->index != -1) {
	IOWriteRegister(COLR_CRT_ADDR, (char)(regInfo->index), regInfo->value);
	regInfo++;
    }
}


- (void)_SVGASetGraphicsControllerRegistersForMode:
	(const CirrusLogicGD542XMode *)cirrusLogicGD542XMode
// Description:	Set all the graphics controller registers for the given mode.
{
    const SVGAIndexValuePair *regInfo;
    
    regInfo = cirrusLogicGD542XMode->graphicsControllerRegisters;
    while (regInfo->index != -1) {
	IOWriteRegister(EIDR_GCR_ADDR, (char)(regInfo->index), regInfo->value);
	regInfo++;
    }
}


- (void)_SVGASetAttributeControllerRegistersForMode:
	(const CirrusLogicGD542XMode *)cirrusLogicGD542XMode
// Description:	Set all the attribute controller registers for the given mode.
{
    const SVGAIndexValuePair *regInfo;

    regInfo = cirrusLogicGD542XMode->attributeControllerRegisters;
    while (regInfo->index != -1) {
	char tmpi;
	inb(READ_COLR_GEN_IN_ST_1);
	tmpi = inb(READ_TOGL_ACR_ADDR);
	tmpi &= ~ACR_MSK;
	tmpi |= (regInfo->index & ACR_MSK);
	outb(WRIT_TOGL_ACR_ADDR, tmpi);
	outb(WRIT_TOGL_ACR_DATA, regInfo->value);
	regInfo++;
    }
}


//
// END:		Implementation of private routines for SVGA
//


//
// BEGIN:	EXPORTED methods
//


- (void)setReadSegment: (unsigned char)segmentNum
// Description:	Select which 64K segment we intend to read from.
{
    outb(0x03ce,0x09);
    outb(0x03cf,(segmentNum << 4));
}


- (void)setWriteSegment: (unsigned char)segmentNum
// Description:	Select which 64K segment we intend to write to.
{
    outb(0x03ce,0x09);
    outb(0x03cf,(segmentNum << 4));
}


- (void)setReadPlane: (unsigned char)planeNum
// Description:	Select which of 4 bit planes to read from in planar
//		modes - only one plane can be active at a time.
{
    char tmp;

    /* Select plane we are reading from */
    tmp = IOReadRegister(EIDR_GCR_ADDR, GCR_AT_READ_MAPS);
    tmp &= ~GCR_AT_RMS;
    tmp |= (planeNum & GCR_AT_RMS);
    IOWriteRegister(EIDR_GCR_ADDR, GCR_AT_READ_MAPS, tmp);
}


- (void)setWritePlane: (unsigned char)planeNum
// Description:	Select one of 4 bit planes to write to in planar modes.
//		Although more than one plane can be active at a time,
//		this routine only allows access to 1 plane at a time.
{
    char tmp, plane = 0x01;

    //
    // Convert plane num to bit enable.
    //
    plane = plane << (planeNum & 0x03);

    //
    // Select plane we are writing to
    //
    tmp = IOReadRegister(EIDR_SEQ_ADDR, SEQ_AT_MPK);
    tmp &= ~(SEQ_AT_EM3 | SEQ_AT_EM2 | SEQ_AT_EM1 | SEQ_AT_EM0);
    tmp |= plane;
    IOWriteRegister(EIDR_SEQ_ADDR, SEQ_AT_MPK, tmp);
}


- (void)savePlaneAndSegmentSettings
// Description:	Saves the current plane and segment settings.
//		This is not a stack push, so we can only save/
//		restore one group of settings at a time.
{
    writePlaneMask = IOReadRegister(EIDR_SEQ_ADDR, SEQ_AT_MPK);
    readPlaneMask = IOReadRegister(EIDR_GCR_ADDR, GCR_AT_READ_MAPS);
    outb(0x3ce,0x09);
    readSegment = inb(0x3cf);
    writeSegment=readSegment=(readSegment >> 4);
}


- (void)restorePlaneAndSegmentSettings
// Description:	Restores the current plane and segment settings.
//		This is not a stack pop, so we can only save/
//		restore one group of settings at a time.
{
    IOWriteRegister(EIDR_SEQ_ADDR, SEQ_AT_MPK, writePlaneMask);
    IOWriteRegister(EIDR_GCR_ADDR, GCR_AT_READ_MAPS, readPlaneMask);
    outb(0x3ce, 0x09);
    outb(0x3cf, (readSegment << 4));
}


- (void)enterSVGAMode
// Description:	Put the display into SVGA mode selectedMode. This
//		typically happens when the window server starts running.
//		We set up all the registers necessary for the given
//		mode and then clear the screen.
{
    IODisplayInfo *displayInfo;
    int totalScreenBytes, bytesLeftToClear, writeSegmentToClear;

    [self _SVGASetGeneralRegistersForMode:
	    modeTable[selectedMode].parameters];
    [self _SVGASetSequencerRegistersForMode:
	    modeTable[selectedMode].parameters];
    [self _SVGASetCrtControllerRegistersForMode:
	    modeTable[selectedMode].parameters];
    [self _SVGASetGraphicsControllerRegistersForMode:
	    modeTable[selectedMode].parameters];
    [self _SVGASetAttributeControllerRegistersForMode:
	    modeTable[selectedMode].parameters];

    //
    // re-enable timing sequencer
    //
    IOWriteRegister(EIDR_SEQ_ADDR, SEQ_AT_CRS, 0x00);
    setColorMapToLinearMonochrome();
    
    //
    // Clear the screen.
    //
    displayInfo = [self displayInfo];
    totalScreenBytes = displayInfo->rowBytes * displayInfo->height;
    for (   bytesLeftToClear = totalScreenBytes, writeSegmentToClear = 0;
	    bytesLeftToClear > 0;
	    bytesLeftToClear -= 0x20000, writeSegmentToClear++) {
	[self setWriteSegment:writeSegmentToClear];
	memset(displayInfo->frameBuffer, 0, MIN(0x20000, bytesLeftToClear));
    }

    [self setWriteSegment:0];
}


- (void)revertToVGAMode
// Description:	Put the display into VGA mode. This typically happens
//		when SoftPC enters full-screen mode.  We set up all the
//		registers necessary for the given mode.
{
    outb(WRIT_EIDR_GEN_MISC_OP, 0xE3);
}


- initFromDeviceDescription:deviceDescription
// Description:	IODevice method.  Initialize the current instance as
//		per the deviceDescription.  Most importantly, this
//		includes selecting the mode and mapping the frame buffer.
{
    IODisplayInfo *displayInfo;
    const IORange *range;
    const CirrusLogicGD542XMode *cirrusLogicGD542XMode;

    if ([super initFromDeviceDescription:deviceDescription] == nil)
	return [super free];

    [self _selectMode];

    range = [deviceDescription memoryRangeList];
    if (range == 0) {
	IOLog("%s: No memory range set.\n", [self name]);
	return [super free];
    }
    videoRamAddress = range[0].start;

    displayInfo = [self displayInfo];
    cirrusLogicGD542XMode = displayInfo->parameters;

    displayInfo->frameBuffer =
        (void *)[self mapFrameBufferAtPhysicalAddress:videoRamAddress
	     length:0x20000];		//SCOTT--fix this
	     
    if (displayInfo->frameBuffer == 0)
	return [super free];

    IOLog("%s: Initialized `%s' @ %d Hz.\n", [self name],
	cirrusLogicGD542XMode->name, displayInfo->refreshRate);

    return self;
}


- setBrightness:(int)level token:(int)t
// Description:	This is from the evScreen protocol. We override our superclass
//		on this since it doesn't know how to set our brightness.
{
    if ( level < EV_SCREEN_MIN_BRIGHTNESS
	|| level > EV_SCREEN_MAX_BRIGHTNESS )
    {
	IOLog("%s: Invalid arg to setBrightness:%d\n",
	    [self name], level );
	    
	if (level < EV_SCREEN_MIN_BRIGHTNESS) {
	    level = EV_SCREEN_MIN_BRIGHTNESS;
	} else {
	    level = EV_SCREEN_MAX_BRIGHTNESS;
	}
    }
    SetBrightness(level);

    return self;
}


//
// END:		EXPORTED methods
//

@end
