/*
 * Id: siscurs.c,v 1.1 1999/11/02 08:17:24 keithp Exp $
 *
 * Copyright © 1999 Keith Packard
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Keith Packard not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Keith Packard makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
/* $XFree86: xc/programs/Xserver/hw/kdrive/sis530/siscurs.c,v 1.2 2000/02/23 20:30:08 dawes Exp $ */

#include "sis.h"
#include "cursorstr.h"

#define SetupCursor(s)	    KdScreenPriv(s); \
			    sisCardInfo(pScreenPriv); \
			    sisScreenInfo(pScreenPriv); \
			    SisPtr sis = sisc->sis; \
			    SisCursor *pCurPriv = &siss->cursor

static void
_sisMoveCursor (ScreenPtr pScreen, int x, int y)
{
    SetupCursor(pScreen);
    CARD8   xlow, xhigh, ylow, yhigh;
    CARD8   xoff, yoff;

    x -= pCurPriv->xhot;
    xoff = 0;
    if (x < 0)
    {
	xoff = -x;
	x = 0;
    }
    y -= pCurPriv->yhot;
    yoff = 0;
    if (y < 0)
    {
	yoff = -y;
	y = 0;
    }
    xlow = (CARD8) x;
    xhigh = (CARD8) (x >> 8);
    ylow = (CARD8) y;
    yhigh = (CARD8) (y >> 8);
    
    PutSrtc (sisc, 0x5, 0x86);
    PutSrtc (sisc, 0x1c, xoff & 0x3f);
    PutSrtc (sisc, 0x1f, yoff & 0x3f);
    
    PutSrtc (sisc, 0x1a, xlow);
    PutSrtc (sisc, 0x1b, xhigh & 0xf);
    
    PutSrtc (sisc, 0x1d, ylow);
    PutSrtc (sisc, 0x1e, yhigh & 0x7);
}

static void
sisMoveCursor (ScreenPtr pScreen, int x, int y)
{
    SetupCursor (pScreen);
    
    if (!pCurPriv->has_cursor)
	return;
    
    if (!pScreenPriv->enabled)
	return;
    
    LockSis(sisc);
    _sisMoveCursor (pScreen, x, y);
    UnlockSis(sisc);
}

static void
_sisSetCursorColors (ScreenPtr pScreen)
{
    SetupCursor (pScreen);
    CursorPtr	pCursor = pCurPriv->pCursor;
    /* set foreground */
    PutSrtc (sisc, 0x17, pCursor->foreRed >> 10);
    PutSrtc (sisc, 0x18, pCursor->foreGreen >> 10);
    PutSrtc (sisc, 0x19, pCursor->foreBlue >> 10);
    
    /* set background */
    PutSrtc (sisc, 0x14, pCursor->backRed >> 10);
    PutSrtc (sisc, 0x15, pCursor->backGreen >> 10);
    PutSrtc (sisc, 0x16, pCursor->backBlue >> 10);
}
    
static void
sisLoadCursor (ScreenPtr pScreen, int x, int y)
{
    SetupCursor(pScreen);
    CursorPtr	    pCursor = pCurPriv->pCursor;
    CursorBitsPtr   bits = pCursor->bits;
    int		    w, h;
    unsigned short  *ram, r;
    unsigned char   *msk, *mskLine, *src, *srcLine;
    unsigned short  m, s;
    
    int		    i, j;
    int		    cursor_address;
    int		    src_stride, src_width;

    CARD8	    sr6;

    /*
     * Lock Sis so the cursor doesn't move while we're setting it
     */
    LockSis(sisc);
    
    pCurPriv->pCursor = pCursor;
    pCurPriv->xhot = pCursor->bits->xhot;
    pCurPriv->yhot = pCursor->bits->yhot;
    
    /*
     * Stick new image into cursor memory
     */
    ram = (unsigned short *) siss->cursor_base;
    mskLine = (unsigned char *) bits->mask;
    srcLine = (unsigned char *) bits->source;

    h = bits->height;
    if (h > SIS_CURSOR_HEIGHT)
	h = SIS_CURSOR_HEIGHT;

    src_stride = BitmapBytePad(bits->width);		/* bytes per line */
    src_width = (bits->width + 7) >> 3;

    for (i = 0; i < SIS_CURSOR_HEIGHT; i++) {
	msk = mskLine;
	src = srcLine;
	mskLine += src_stride;
	srcLine += src_stride;
	for (j = 0; j < SIS_CURSOR_WIDTH / 8; j++) {

	    unsigned short  m, s;

	    if (i < h && j < src_width) 
	    {
		m = *msk++;
		s = *src++ & m;
		m = ~m;
		/* mask off right side */
		if (j == src_width - 1 && (bits->width & 7))
		{
		    m |= 0xff << (bits->width & 7);
		}
	    }
	    else
	    {
		m = 0xff;
		s = 0x00;
	    }

	    /*
	     * The SIS530 HW cursor format is: source(AND) bit, 
	     *   then a mask(XOR) bit, etc.
	     * byte swapping in sis530 is:
	     *   abcd ==> cdab
	     */

#define bit(a,n)    (((a) >> (n)) & 0x1)
	    
	    r = ((bit(m, 0) <<  7) | (bit(s, 0) <<  6) |
		 (bit(m, 1) <<  5) | (bit(s, 1) <<  4) |
		 (bit(m, 2) <<  3) | (bit(s, 2) <<  2) |
		 (bit(m, 3) <<  1) | (bit(s, 3) <<  0) |
		 (bit(m, 4) << 15) | (bit(s, 4) << 14) |
		 (bit(m, 5) << 13) | (bit(s, 5) << 12) |
		 (bit(m, 6) << 11) | (bit(s, 6) << 10) |
		 (bit(m, 7) <<  9) | (bit(s, 7) <<  8));

	    *ram++ = r;
	}
    }
    
    /* Set new color */
    _sisSetCursorColors (pScreen);
     
    /* Move to new position */
    _sisMoveCursor (pScreen, x, y);
    
    /* Enable cursor */
    sr6 = GetSrtc (sisc, 0x6);
    sr6 |= 0x40;
    PutSrtc (sisc, 0x6, sr6);

    UnlockSis(sisc);
}

static void
sisUnloadCursor (ScreenPtr pScreen)
{
    SetupCursor (pScreen);
    CARD8	sr6;

    LockSis (sisc);

    /* Disable cursor */
    sr6 = GetSrtc (sisc, 0x6);
    sr6 &= ~0x40;
    PutSrtc (sisc, 0x6, sr6);
    PutSrtc (sisc, 0x1b, 0x00);
    
    UnlockSis (sisc);
}

static Bool
sisRealizeCursor (ScreenPtr pScreen, CursorPtr pCursor)
{
    SetupCursor(pScreen);

    if (!pScreenPriv->enabled)
	return TRUE;
    
    /* miRecolorCursor does this */
    if (pCurPriv->pCursor == pCursor)
    {
	if (pCursor)
	{
	    int	    x, y;
	    
	    miPointerPosition (&x, &y);
	    sisLoadCursor (pScreen, x, y);
	}
    }
    return TRUE;
}

static Bool
sisUnrealizeCursor (ScreenPtr pScreen, CursorPtr pCursor)
{
    return TRUE;
}

static void
sisSetCursor (ScreenPtr pScreen, CursorPtr pCursor, int x, int y)
{
    SetupCursor(pScreen);

    pCurPriv->pCursor = pCursor;
    
    if (!pScreenPriv->enabled)
	return;
    
    if (pCursor)
	sisLoadCursor (pScreen, x, y);
    else
	sisUnloadCursor (pScreen);
}

miPointerSpriteFuncRec sisPointerSpriteFuncs = {
    sisRealizeCursor,
    sisUnrealizeCursor,
    sisSetCursor,
    sisMoveCursor,
};

static void
sisQueryBestSize (int class, 
		 unsigned short *pwidth, unsigned short *pheight, 
		 ScreenPtr pScreen)
{
    SetupCursor (pScreen);

    switch (class)
    {
    case CursorShape:
	if (*pwidth > pCurPriv->width)
	    *pwidth = pCurPriv->width;
	if (*pheight > pCurPriv->height)
	    *pheight = pCurPriv->height;
	if (*pwidth > pScreen->width)
	    *pwidth = pScreen->width;
	if (*pheight > pScreen->height)
	    *pheight = pScreen->height;
	break;
    default:
	fbQueryBestSize (class, pwidth, pheight, pScreen);
	break;
    }
}

Bool
sisCursorInit (ScreenPtr pScreen)
{
    SetupCursor (pScreen);

    if (!siss->cursor_base)
    {
	pCurPriv->has_cursor = FALSE;
	return FALSE;
    }
    
    pCurPriv->width = SIS_CURSOR_WIDTH;
    pCurPriv->height= SIS_CURSOR_HEIGHT;
    pScreen->QueryBestSize = sisQueryBestSize;
    miPointerInitialize (pScreen,
			 &sisPointerSpriteFuncs,
			 &kdPointerScreenFuncs,
			 FALSE);
    pCurPriv->has_cursor = TRUE;
    pCurPriv->pCursor = NULL;
    return TRUE;
}

void
sisCursorEnable (ScreenPtr pScreen)
{
    SetupCursor (pScreen);

    if (pCurPriv->has_cursor)
    {
	if (pCurPriv->pCursor)
	{
	    int	    x, y;
	    
	    miPointerPosition (&x, &y);
	    sisLoadCursor (pScreen, x, y);
	}
	else
	    sisUnloadCursor (pScreen);
    }
}

void
sisCursorDisable (ScreenPtr pScreen)
{
    SetupCursor (pScreen);

    if (!pScreenPriv->enabled)
	return;
    
    if (pCurPriv->has_cursor)
    {
	if (pCurPriv->pCursor)
	{
	    sisUnloadCursor (pScreen);
	}
    }
}

void
sisCursorFini (ScreenPtr pScreen)
{
    SetupCursor (pScreen);

    pCurPriv->pCursor = NULL;
}
