#define DEBUG_VERB 2

/* $XFree86: xc/programs/Xserver/hw/xfree86/vbe/vbeModes.c,v 1.9 2005/08/28 20:04:52 tsi Exp $ */

/*
 * Copyright © 2002 David Dawes
 *
 * 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
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * 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 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 NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHOR(S) 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.
 *
 * Except as contained in this notice, the name of the author(s) shall
 * not be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization from
 * the author(s).
 *
 * Authors: David Dawes <dawes@xfree86.org>
 *
 */
/*
 * Copyright (c) 2002-2005 by The XFree86 Project, Inc.
 * 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 the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, 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:
 *
 *   1.  Redistributions of source code must retain the above copyright
 *       notice, this list of conditions, and the following disclaimer.
 *
 *   2.  Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer
 *       in the documentation and/or other materials provided with the
 *       distribution, and in the same place and form as other copyright,
 *       license and disclaimer information.
 *
 *   3.  The end-user documentation included with the redistribution,
 *       if any, must include the following acknowledgment: "This product
 *       includes software developed by The XFree86 Project, Inc
 *       (http://www.xfree86.org/) and its contributors", in the same
 *       place and form as other third-party acknowledgments.  Alternately,
 *       this acknowledgment may appear in the software itself, in the
 *       same form and location as other such third-party acknowledgments.
 *
 *   4.  Except as contained in this notice, the name of The XFree86
 *       Project, Inc shall not be used in advertising or otherwise to
 *       promote the sale, use or other dealings in this Software without
 *       prior written authorization from The XFree86 Project, Inc.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE XFREE86 PROJECT, INC OR ITS CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "vbeModes.h"

static int
GetDepthFlag(vbeInfoPtr pVbe, int id)
{
    VbeModeInfoBlock *mode;
    int bpp;

    if ((mode = VBEGetModeInfo(pVbe, id)) == NULL)
	return 0;

    if (VBE_MODE_USABLE(mode, 0)) {
	int depth;

	if (VBE_MODE_COLOR(mode)) {
	    depth = mode->RedMaskSize + mode->GreenMaskSize +
		    mode->BlueMaskSize;
	} else {
	    depth = 1;
	}
	bpp = mode->BitsPerPixel;
	VBEFreeModeInfo(mode);
	mode = NULL;
	switch (depth) {
	case 1:
	    return V_DEPTH_1;
	case 4:
	    return V_DEPTH_4;
	case 8:
	    return V_DEPTH_8;
	case 15:
	    return V_DEPTH_15;
	case 16:
	    return V_DEPTH_16;
	case 24:
	    switch (bpp) {
	    case 24:
		return V_DEPTH_24_24;
	    case 32:
		return V_DEPTH_24_32;
	    }
	}
    }
    if (mode)
	VBEFreeModeInfo(mode);
    return 0;
}

/*
 * Find supported mode depths.
 */
int
VBEFindSupportedDepths(vbeInfoPtr pVbe, VbeInfoBlock *vbe, int *flags24,
			int modeTypes)
{
    int i = 0;
    int depths = 0;

    if (modeTypes & V_MODETYPE_VBE) {
	while (vbe->VideoModePtr[i] != 0xffff) {
	    depths |= GetDepthFlag(pVbe, vbe->VideoModePtr[i++]);
	}
    }

    /*
     * XXX This possibly only works with VBE 3.0 and later.
     */
    if (modeTypes & V_MODETYPE_VGA) {
	for (i = 0; i < 0x7F; i++) {
	    depths |= GetDepthFlag(pVbe, i);
	}
    }

    if (flags24) {
	if (depths & V_DEPTH_24_24)
	    *flags24 |= Support24bppFb;
	if (depths & V_DEPTH_24_32)
	    *flags24 |= Support32bppFb;
    }

    return depths;
}

static DisplayModePtr
CheckMode(ScrnInfoPtr pScrn, vbeInfoPtr pVbe, VbeInfoBlock *vbe, int id,
	  int flags)
{
    CARD16 major;
    VbeModeInfoBlock *mode;
    DisplayModePtr pMode, p;
    VbeModeInfoData *data;
    Bool modeOK = FALSE;
    ModeStatus status = MODE_OK;
    const char *prefix = "";

    major = (unsigned)(vbe->VESAVersion >> 8);

    if ((mode = VBEGetModeInfo(pVbe, id)) == NULL)
	return NULL;

    /* Does the mode match the depth/bpp? */
    /* Some BIOS's set BitsPerPixel to 15 instead of 16 for 15/16 */
    if (VBE_MODE_USABLE(mode, flags) &&
	((pScrn->bitsPerPixel == 1 && !VBE_MODE_COLOR(mode)) ||
	 (mode->BitsPerPixel > 8 &&
	  (mode->RedMaskSize + mode->GreenMaskSize +
	   mode->BlueMaskSize) == pScrn->depth &&
	  mode->BitsPerPixel == pScrn->bitsPerPixel) ||
	 (mode->BitsPerPixel == 15 && pScrn->depth == 15) ||
	 (mode->BitsPerPixel <= 8 &&
	  mode->BitsPerPixel == pScrn->bitsPerPixel))) {
	modeOK = TRUE;
	prefix = "*";
    }

    /*
     * Check if there's a valid monitor mode that this one can be matched
     * up with.  The actual matching is done later.
     */
    if (modeOK) {
	Bool sizeMatch = FALSE;
	modeOK = FALSE;
	for (p = pScrn->monitor->Modes; p != NULL; p = p->next) {
	    if ((p->HDisplay != mode->XResolution) ||
		(p->VDisplay != mode->YResolution) ||
		(p->Flags & (V_INTERLACE | V_DBLSCAN | V_CLKDIV2)))
		continue;
	    sizeMatch = TRUE;
	    /* XXX could support the various V_ flags */
	    status = xf86CheckModeForMonitor(p, pScrn->monitor);
	    if (status == MODE_OK) {
		modeOK = TRUE;
		break;
	    }
	}
	if (sizeMatch && !modeOK) {
	    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
		       "Not using built-in mode \"%dx%d\" (%s)\n",
			mode->XResolution, mode->YResolution,
			xf86ModeStatusToString(status));
	}
    }

    xf86ErrorFVerb(DEBUG_VERB, "%sMode: %x (%dx%d)\n",
	prefix, id, mode->XResolution, mode->YResolution);
    xf86ErrorFVerb(DEBUG_VERB, "	ModeAttributes: 0x%x\n",
	mode->ModeAttributes);
    xf86ErrorFVerb(DEBUG_VERB, "	WinAAttributes: 0x%x\n",
	mode->WinAAttributes);
    xf86ErrorFVerb(DEBUG_VERB, "	WinBAttributes: 0x%x\n",
	mode->WinBAttributes);
    xf86ErrorFVerb(DEBUG_VERB, "	WinGranularity: %d\n",
	mode->WinGranularity);
    xf86ErrorFVerb(DEBUG_VERB, "	WinSize: %d\n",
	mode->WinSize);
    xf86ErrorFVerb(DEBUG_VERB, "	WinASegment: 0x%x\n",
	mode->WinASegment);
    xf86ErrorFVerb(DEBUG_VERB, "	WinBSegment: 0x%x\n",
	mode->WinBSegment);
    xf86ErrorFVerb(DEBUG_VERB, "	WinFuncPtr: 0x%lx\n",
	(unsigned long)mode->WinFuncPtr);
    xf86ErrorFVerb(DEBUG_VERB, "	BytesPerScanline: %d\n",
	mode->BytesPerScanline);
    xf86ErrorFVerb(DEBUG_VERB, "	XResolution: %d\n",
	mode->XResolution);
    xf86ErrorFVerb(DEBUG_VERB, "	YResolution: %d\n",
	mode->YResolution);
    xf86ErrorFVerb(DEBUG_VERB, "	XCharSize: %d\n",
	mode->XCharSize);
    xf86ErrorFVerb(DEBUG_VERB, "	YCharSize: %d\n",
	mode->YCharSize);
    xf86ErrorFVerb(DEBUG_VERB, "	NumberOfPlanes: %d\n",
	mode->NumberOfPlanes);
    xf86ErrorFVerb(DEBUG_VERB, "	BitsPerPixel: %d\n",
	mode->BitsPerPixel);
    xf86ErrorFVerb(DEBUG_VERB, "	NumberOfBanks: %d\n",
	mode->NumberOfBanks);
    xf86ErrorFVerb(DEBUG_VERB, "	MemoryModel: %d\n",
	mode->MemoryModel);
    xf86ErrorFVerb(DEBUG_VERB, "	BankSize: %d\n",
	mode->BankSize);
    xf86ErrorFVerb(DEBUG_VERB, "	NumberOfImages: %d\n",
	mode->NumberOfImages);
    xf86ErrorFVerb(DEBUG_VERB, "	RedMaskSize: %d\n",
	mode->RedMaskSize);
    xf86ErrorFVerb(DEBUG_VERB, "	RedFieldPosition: %d\n",
	mode->RedFieldPosition);
    xf86ErrorFVerb(DEBUG_VERB, "	GreenMaskSize: %d\n",
	mode->GreenMaskSize);
    xf86ErrorFVerb(DEBUG_VERB, "	GreenFieldPosition: %d\n",
	mode->GreenFieldPosition);
    xf86ErrorFVerb(DEBUG_VERB, "	BlueMaskSize: %d\n",
	mode->BlueMaskSize);
    xf86ErrorFVerb(DEBUG_VERB, "	BlueFieldPosition: %d\n",
	mode->BlueFieldPosition);
    xf86ErrorFVerb(DEBUG_VERB, "	RsvdMaskSize: %d\n",
	mode->RsvdMaskSize);
    xf86ErrorFVerb(DEBUG_VERB, "	RsvdFieldPosition: %d\n",
	mode->RsvdFieldPosition);
    xf86ErrorFVerb(DEBUG_VERB, "	DirectColorModeInfo: %d\n",
	mode->DirectColorModeInfo);
    if (major >= 2) {
	xf86ErrorFVerb(DEBUG_VERB, "	PhysBasePtr: 0x%lx\n",
	    (unsigned long)mode->PhysBasePtr);
	if (major >= 3) {
	    xf86ErrorFVerb(DEBUG_VERB, "	LinBytesPerScanLine: %d\n",
		mode->LinBytesPerScanLine);
	    xf86ErrorFVerb(DEBUG_VERB, "	BnkNumberOfImagePages: %d\n",
		mode->BnkNumberOfImagePages);
	    xf86ErrorFVerb(DEBUG_VERB, "	LinNumberOfImagePages: %d\n",
		mode->LinNumberOfImagePages);
	    xf86ErrorFVerb(DEBUG_VERB, "	LinRedMaskSize: %d\n",
		mode->LinRedMaskSize);
	    xf86ErrorFVerb(DEBUG_VERB, "	LinRedFieldPosition: %d\n",
		mode->LinRedFieldPosition);
	    xf86ErrorFVerb(DEBUG_VERB, "	LinGreenMaskSize: %d\n",
		mode->LinGreenMaskSize);
	    xf86ErrorFVerb(DEBUG_VERB, "	LinGreenFieldPosition: %d\n",
		mode->LinGreenFieldPosition);
	    xf86ErrorFVerb(DEBUG_VERB, "	LinBlueMaskSize: %d\n",
		mode->LinBlueMaskSize);
	    xf86ErrorFVerb(DEBUG_VERB, "	LinBlueFieldPosition: %d\n",
		mode->LinBlueFieldPosition);
	    xf86ErrorFVerb(DEBUG_VERB, "	LinRsvdMaskSize: %d\n",
		mode->LinRsvdMaskSize);
	    xf86ErrorFVerb(DEBUG_VERB, "	LinRsvdFieldPosition: %d\n",
		mode->LinRsvdFieldPosition);
	    xf86ErrorFVerb(DEBUG_VERB, "	MaxPixelClock: %ld\n",
		(unsigned long)mode->MaxPixelClock);
	}
    }

    if (!modeOK) {
	VBEFreeModeInfo(mode);
	return NULL;
    }
    pMode = xnfcalloc(sizeof(DisplayModeRec), 1);

    pMode->status = MODE_OK;
    pMode->type = M_T_BUILTIN;

    /* for adjust frame */
    pMode->HDisplay = mode->XResolution;
    pMode->VDisplay = mode->YResolution;

    data = xnfcalloc(sizeof(VbeModeInfoData), 1);
    data->mode = id;
    data->data = mode;
    pMode->PrivSize = sizeof(VbeModeInfoData);
    pMode->Private = (INT32*)data;
    pMode->next = NULL;
    return pMode;
}

/*
 * Check the available BIOS modes, and extract those that match the
 * requirements into the modePool.  Note: modePool is a NULL-terminated
 * list.
 */

DisplayModePtr
VBEGetModePool(ScrnInfoPtr pScrn, vbeInfoPtr pVbe, VbeInfoBlock *vbe,
	       int modeTypes)
{
    DisplayModePtr pMode, p = NULL, modePool = NULL;
    int i = 0;

    xf86SetMonitorParameters(pScrn, pScrn->monitor, 0, 0, 0);
    xf86AddEDIDModes(pScrn, pScrn->monitor, 0);
    if (modeTypes & V_MODETYPE_VBE) {
	while (vbe->VideoModePtr[i] != 0xffff) {
	    int id = vbe->VideoModePtr[i++];

	    if ((pMode = CheckMode(pScrn, pVbe, vbe, id, modeTypes)) != NULL) {
		ModeStatus status = MODE_OK;

		/* Check the mode against a specified virtual size (if any) */
		if (pScrn->display->virtualX > 0 &&
		    pMode->HDisplay > pScrn->display->virtualX) {
		    status = MODE_VIRTUAL_X;
		}
		if (pScrn->display->virtualY > 0 &&
		    pMode->VDisplay > pScrn->display->virtualY) {
		    status = MODE_VIRTUAL_Y;
		}
		if (status != MODE_OK) {
		     xf86DrvMsg(pScrn->scrnIndex, X_INFO,
				"Not using mode \"%dx%d\" (%s)\n",
				pMode->HDisplay, pMode->VDisplay,
				xf86ModeStatusToString(status));
		} else {
		    if (p == NULL) {
			modePool = pMode;
		    } else {
			p->next = pMode;
		    }
		    pMode->prev = NULL;
		    p = pMode;
		}
	    }
	}
    }
    if (modeTypes & V_MODETYPE_VGA) {
	for (i = 0; i < 0x7F; i++) {
	    if ((pMode = CheckMode(pScrn, pVbe, vbe, i, modeTypes)) != NULL) {
		ModeStatus status = MODE_OK;

		/* Check the mode against a specified virtual size (if any) */
		if (pScrn->display->virtualX > 0 &&
		    pMode->HDisplay > pScrn->display->virtualX) {
		    status = MODE_VIRTUAL_X;
		}
		if (pScrn->display->virtualY > 0 &&
		    pMode->VDisplay > pScrn->display->virtualY) {
		    status = MODE_VIRTUAL_Y;
		}
		if (status != MODE_OK) {
		     xf86DrvMsg(pScrn->scrnIndex, X_INFO,
				"Not using mode \"%dx%d\" (%s)\n",
				pMode->HDisplay, pMode->VDisplay,
				xf86ModeStatusToString(status));
		} else {
		    if (p == NULL) {
			modePool = pMode;
		    } else {
			p->next = pMode;
		    }
		    pMode->prev = NULL;
		    p = pMode;
		}
	    }
	}
    }
    return modePool;
}

#ifndef MODENAMESIZE
#define MODENAMESIZE (4 + 1 + 4 + 1)
#endif

void
VBESetModeNames(DisplayModePtr pMode)
{
    if (!pMode)
	return;

    do {
	if (!pMode->name) {
	    /* Catch "bad" modes. */
	    if (pMode->HDisplay > 10000 || pMode->HDisplay < 0 ||
		pMode->VDisplay > 10000 || pMode->VDisplay < 0) {
		pMode->name = strdup("BADMODE");
	    } else {
		pMode->name = xnfalloc(MODENAMESIZE);
		snprintf(pMode->name, MODENAMESIZE, "%dx%d",
			 pMode->HDisplay, pMode->VDisplay);
	    }
	}
	pMode = pMode->next;
    } while (pMode);
}

/*
 * Go through the monitor modes and selecting the best set of
 * parameters for each BIOS mode.  Note: This is only supported in
 * VBE version 3.0 or later.
 */
void
VBESetModeParameters(ScrnInfoPtr pScrn, vbeInfoPtr pVbe)
{
    DisplayModePtr pMode;
    VbeModeInfoData *data;

    pMode = pScrn->modes;
    do {
	DisplayModePtr p, best = NULL;
	ModeStatus status;

	for (p = pScrn->monitor->Modes; p != NULL; p = p->next) {
	    if ((p->HDisplay != pMode->HDisplay) ||
		(p->VDisplay != pMode->VDisplay) ||
		(p->Flags & (V_INTERLACE | V_DBLSCAN | V_CLKDIV2)))
		continue;
	    /* XXX could support the various V_ flags */
	    status = xf86CheckModeForMonitor(p, pScrn->monitor);
	    if (status != MODE_OK)
		continue;
	    if (!best || (p->Clock > best->Clock))
		best = p;
	}

	if (best) {
	    int clock;

	    data = (VbeModeInfoData*)pMode->Private;
	    pMode->HSync = (float)best->Clock * 1000.0 / best->HTotal + 0.5;
	    pMode->VRefresh = pMode->HSync / best->VTotal + 0.5;
	    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
		       "Attempting to use %dHz refresh for mode \"%s\" (%x)\n",
		       (int)pMode->VRefresh, pMode->name, data->mode);
	    data->block = xcalloc(sizeof(VbeCRTCInfoBlock), 1);
	    data->block->HorizontalTotal = best->HTotal;
	    data->block->HorizontalSyncStart = best->HSyncStart;
	    data->block->HorizontalSyncEnd = best->HSyncEnd;
	    data->block->VerticalTotal = best->VTotal;
	    data->block->VerticalSyncStart = best->VSyncStart;
	    data->block->VerticalSyncEnd = best->VSyncEnd;
	    data->block->Flags = ((best->Flags & V_NHSYNC) ? CRTC_NHSYNC : 0) |
				 ((best->Flags & V_NVSYNC) ? CRTC_NVSYNC : 0);
	    data->block->PixelClock = best->Clock * 1000;
	    /* XXX May not have this. */
	    clock = VBEGetPixelClock(pVbe, data->mode, data->block->PixelClock);
#ifdef DEBUG
	    ErrorF("Setting clock %.2fMHz, closest is %.2fMHz\n",
		(double)data->block->PixelClock / 1000000.0,
		(double)clock / 1000000.0);
#endif
	    if (clock)
		data->block->PixelClock = clock;
	    data->mode |= (1 << 11);
	    data->block->RefreshRate = ((double)(data->block->PixelClock) /
		       (double)(best->HTotal * best->VTotal)) * 100;
	}
	pMode = pMode->next;
    } while (pMode != pScrn->modes);
}

/*
 * These wrappers are to allow (temporary) funtionality divergences.
 */
int
VBEValidateModes(ScrnInfoPtr scrp, DisplayModePtr availModes,
		  char **modeNames, ClockRangePtr clockRanges,
		  int *linePitches, int minPitch, int maxPitch, int pitchInc,
		  int minHeight, int maxHeight, int virtualX, int virtualY,
		  int apertureSize, LookupModeFlags strategy)
{
    return xf86ValidateModes(scrp, availModes, modeNames, clockRanges,
			     linePitches, minPitch, maxPitch, pitchInc,
			     minHeight, maxHeight, virtualX, virtualY,
			     apertureSize, strategy);
}

void
VBEPrintModes(ScrnInfoPtr scrp)
{
    xf86PrintModes(scrp);
}
