/*****************************************************************************
 * VIA Unichrome XvMC extension client lib.
 *
 * Copyright (c) 2004 Thomas Hellström. All rights reserved.
 * Copyright (c) 2003 Andreas Robinson. 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:
 *
 * 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) OR COPYRIGHT HOLDER(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.
 */


/*
 * Low-level functions that deal directly with the hardware. In the future,
 * these functions might be implemented in a kernel module. Also, some of them
 * would benefit from DMA.
 *
 * Authors: 
 *  Andreas Robinson 2003. (Initial decoder interface functions).
 *  Thomas Hellstrom 2004, 2005 (Blitting functions, AGP and locking, Unichrome Pro Video AGP).
 *  Ivor Hewitt 2005 (Unichrome Pro modifications and merging).
 */

/* IH
 * I've left the proReg or-ing in case we need/want to implement the V1/V3
 * register toggle too, which also moves the register locations.
 * The CN400 has dual mpeg decoders, not sure at the moment whether these
 * are also operated through independent registers also.
 */

#undef VIDEO_DMA
#define HQV_USE_IRQ
#define UNICHROME_PRO

#include "viaXvMCPriv.h"
#include "viaLowLevel.h"
#include "driDrawable.h"
#include <time.h>
#include <sys/time.h>
#include <stdio.h>

typedef enum {ll_init, ll_agpBuf, ll_pciBuf, ll_timeStamp, ll_llBuf} 
    LLState;


typedef struct {
    drm_via_mem_t mem;
    unsigned offset;
    unsigned stride;
    unsigned height;
} LowLevelBuffer;

struct _XvMCLowLevel;

typedef struct _ViaCommandBuffer {
    CARD32 *buf;
    CARD32 waitFlags;
    unsigned pos;
    unsigned bufSize;
    int mode;
    int header_start;
    int rindex;
    void (*flushFunc)(struct _ViaCommandBuffer *cb, struct _XvMCLowLevel *xl);
} ViaCommandBuffer;
    
typedef struct _XvMCLowLevel{
    ViaCommandBuffer agpBuf, pciBuf, *videoBuf;
    int use_agp;   
    int fd;
    drm_context_t *drmcontext;
    drmLockPtr hwLock;
    drmAddress mmioAddress;
    drmAddress fbAddress;
    unsigned fbStride;
    unsigned fbDepth;
    unsigned width;
    unsigned height;
    int performLocking;
    unsigned errors;
    drm_via_mem_t tsMem;
    CARD32 tsOffset;
    volatile CARD32 *tsP;
    CARD32 curTimeStamp;
    CARD32 lastReadTimeStamp;
    int agpSync;
    CARD32 agpSyncTimeStamp;
    unsigned chipId;

    /*
     * Data for video-engine less display
     */

    XvMCRegion sRegion;
    XvMCRegion dRegion;
    LowLevelBuffer scale;
    LowLevelBuffer back;
    Bool downScaling;
    CARD32 downScaleW;
    CARD32 downScaleH;
    CARD32 upScaleW;
    CARD32 upScaleH;
    unsigned fetch;
    unsigned line;
    LLState state;
}XvMCLowLevel;


/*
 * For Other architectures than i386 these might have to be modified for
 * bigendian etc.
 */


#define MPEGIN(xl,reg)							\
    *((volatile CARD32 *)(((CARD8 *)(xl)->mmioAddress) + 0xc00 + (reg)))

#define VIDIN(ctx,reg)							\
    *((volatile CARD32 *)(((CARD8 *)(ctx)->mmioAddress) + 0x200 + (reg)))

#define REGIN(ctx,reg)							\
    *((volatile CARD32 *)(((CARD8 *)(ctx)->mmioAddress) + 0x0000 + (reg)))

#define HQV_CONTROL             0x1D0
#define HQV_SRC_OFFSET          0x1CC
#define HQV_SRC_STARTADDR_Y     0x1D4
#define HQV_SRC_STARTADDR_U     0x1D8
#define HQV_SRC_STARTADDR_V     0x1DC
#define HQV_MINIFY_DEBLOCK      0x1E8    

#define REG_HQV1_INDEX      0x00001000
    
#define HQV_SW_FLIP         0x00000010
#define HQV_FLIP_STATUS     0x00000001
#define HQV_SUBPIC_FLIP     0x00008000
#define HQV_FLIP_ODD        0x00000020
#define HQV_DEINTERLACE     0x00010000
#define HQV_FIELD_2_FRAME   0x00020000
#define HQV_FRAME_2_FIELD   0x00040000
#define HQV_FIELD_UV        0x00100000
#define HQV_DEBLOCK_HOR     0x00008000
#define HQV_DEBLOCK_VER     0x80000000
#define HQV_YUV420          0xC0000000
#define HQV_YUV422          0x80000000
#define HQV_ENABLE          0x08000000
#define HQV_GEN_IRQ         0x00000080

#define HQV_SCALE_ENABLE    0x00000800
#define HQV_SCALE_DOWN      0x00001000

#define V_COMPOSE_MODE          0x98
#define V1_COMMAND_FIRE         0x80000000  
#define V3_COMMAND_FIRE         0x40000000  

/* SUBPICTURE Registers */
#define SUBP_CONTROL_STRIDE     0x1C0
#define SUBP_STARTADDR          0x1C4
#define RAM_TABLE_CONTROL       0x1C8
#define RAM_TABLE_READ          0x1CC

/* SUBP_CONTROL_STRIDE              0x3c0 */
#define SUBP_HQV_ENABLE             0x00010000
#define SUBP_IA44                   0x00020000
#define SUBP_AI44                   0x00000000
#define SUBP_STRIDE_MASK            0x00001fff
#define SUBP_CONTROL_MASK           0x00070000

/* RAM_TABLE_CONTROL                0x3c8 */
#define RAM_TABLE_RGB_ENABLE        0x00000007


#define VIA_REG_STATUS          0x400
#define VIA_REG_GEMODE          0x004
#define VIA_REG_SRCBASE         0x030
#define VIA_REG_DSTBASE         0x034
#define VIA_REG_PITCH           0x038      
#define VIA_REG_SRCCOLORKEY     0x01C      
#define VIA_REG_KEYCONTROL      0x02C       
#define VIA_REG_SRCPOS          0x008
#define VIA_REG_DSTPOS          0x00C
#define VIA_REG_GECMD           0x000
#define VIA_REG_DIMENSION       0x010       /* width and height */
#define VIA_REG_FGCOLOR         0x018


#define VIA_VR_QUEUE_BUSY       0x00020000 /* Virtual Queue is busy */
#define VIA_CMD_RGTR_BUSY       0x00000080  /* Command Regulator is busy */
#define VIA_2D_ENG_BUSY         0x00000001  /* 2D Engine is busy */
#define VIA_3D_ENG_BUSY         0x00000002  /* 3D Engine is busy */
#define VIA_GEM_8bpp            0x00000000
#define VIA_GEM_16bpp           0x00000100
#define VIA_GEM_32bpp           0x00000300
#define VIA_GEC_BLT             0x00000001
#define VIA_PITCH_ENABLE        0x80000000
#define VIA_GEC_INCX            0x00000000
#define VIA_GEC_DECY            0x00004000
#define VIA_GEC_INCY            0x00000000
#define VIA_GEC_DECX            0x00008000
#define VIA_GEC_FIXCOLOR_PAT    0x00002000


#define VIA_BLIT_CLEAR 0x00
#define VIA_BLIT_COPY 0xCC
#define VIA_BLIT_FILL 0xF0
#define VIA_BLIT_SET 0xFF

#define VIA_SYNCWAITTIMEOUT 50000 /* Might be a bit conservative */
#define VIA_DMAWAITTIMEOUT 150000
#define VIA_VIDWAITTIMEOUT 50000
#define VIA_XVMC_DECODERTIMEOUT 50000 /*(microseconds)*/

#define VIA_AGP_HEADER5 0xFE040000
#define VIA_AGP_HEADER6 0xFE050000

typedef struct{
    CARD32 data;
    Bool set;
} HQVRegister;


#define H1_ADDR(val) (((val) >> 2) | 0xF0000000)
#define WAITFLAGS(cb, flags)			\
    (cb)->waitFlags |= (flags)
#define BEGIN_RING_AGP(cb, xl, size)					\
    do {								\
	if ((cb)->pos > ((cb)->bufSize-(size))) {			\
	    cb->flushFunc(cb, xl);					\
	}								\
    } while(0)
#define OUT_RING_AGP(cb, val) do{			\
	(cb)->buf[(cb)->pos++] = (val);	\
  } while(0);		

#define OUT_RING_QW_AGP(cb, val1, val2)			\
    do {						\
	(cb)->buf[(cb)->pos++] = (val1);	\
	(cb)->buf[(cb)->pos++] = (val2);	\
    } while (0)
  

#define BEGIN_HEADER5_AGP(cb, xl, index)	\
    do {					\
	BEGIN_RING_AGP(cb, xl, 8);		\
	(cb)->mode = VIA_AGP_HEADER5;		\
        (cb)->rindex = (index);			\
	(cb)->header_start = (cb)->pos;		\
	(cb)->pos += 4;				\
    } while (0)

#define BEGIN_HEADER6_AGP(cb, xl)		\
    do {					\
	BEGIN_RING_AGP(cb, xl, 8);		\
	(cb)->mode = VIA_AGP_HEADER6;	\
	(cb)->header_start = (cb)->pos; \
	(cb)->pos += 4;			\
    } while (0)

#define BEGIN_HEADER5_DATA(cb, xl, size, index)				\
    do {								\
	if ((cb)->pos > ((cb)->bufSize - ((size) + 16))) {		\
	    cb->flushFunc(cb, xl);					\
	    BEGIN_HEADER5_AGP(cb, xl, index);				\
	} else if ((cb)->mode && (((cb)->mode != VIA_AGP_HEADER5) ||	\
				  ((cb)->rindex != index))) {		\
	    finish_header_agp(cb);					\
	    BEGIN_HEADER5_AGP((cb), xl, (index));			\
	} else if (cb->mode != VIA_AGP_HEADER5) {			\
	    BEGIN_HEADER5_AGP((cb), xl, (index)); 			\
	}								\
    }while(0)

#define BEGIN_HEADER6_DATA(cb, xl, size)				\
    do{									\
	if ((cb)->pos > (cb->bufSize-(((size) << 1) + 16))) {		\
	    cb->flushFunc(cb, xl);					\
	    BEGIN_HEADER6_AGP(cb, xl);					\
	} else	if ((cb)->mode && ((cb)->mode != VIA_AGP_HEADER6)) {	\
	    finish_header_agp(cb);					\
	    BEGIN_HEADER6_AGP(cb, xl);					\
	}								\
	else if ((cb->mode != VIA_AGP_HEADER6)) {			\
	    BEGIN_HEADER6_AGP(cb, (xl));				\
	}								\
    }while(0)

#define HQV_SHADOW_BASE 0x3CC
#define HQV_SHADOW_SIZE 13

#define SETHQVSHADOW(shadow, offset, value)				\
    do {								\
	HQVRegister *r = (shadow) + (((offset) - HQV_SHADOW_BASE) >> 2); \
	r->data = (value);						\
	r->set = TRUE;							\
    } while(0)

#define GETHQVSHADOW(shadow, offset)  ((shadow)[(offset - HQV_SHADOW_BASE) >> 2].data)

#define LL_HW_LOCK(xl)							\
    do {								\
	DRM_LOCK((xl)->fd,(xl)->hwLock,*(xl)->drmcontext,0);		\
    } while(0);
#define LL_HW_UNLOCK(xl)					\
    do {							\
	DRM_UNLOCK((xl)->fd,(xl)->hwLock,*(xl)->drmcontext);	\
    } while(0);

static HQVRegister hqvShadow[HQV_SHADOW_SIZE];


static void 
initHQVShadow(HQVRegister *r)
{
    int i;
    
    for(i=0; i<HQV_SHADOW_SIZE; ++i) {
	r->data = 0;
	r++->set = FALSE;
    }
}

static void
setHQVDeblocking(HQVRegister *shadow, Bool on, Bool lowPass) 
{
    CARD32 tmp = GETHQVSHADOW(shadow, 0x3DC);
    
    if (!on) {
	tmp &= ~(1 << 27);
	SETHQVSHADOW(shadow, 0x3DC , tmp);
	return;
    }

    tmp |= (8 << 16) | (1 << 27);
    if (lowPass) 
	tmp |= (1 << 26);
    SETHQVSHADOW(shadow, 0x3DC , tmp);

    tmp = GETHQVSHADOW(shadow, 0x3D4);
    tmp |= (6 << 27);
    SETHQVSHADOW(shadow, 0x3D4, tmp);

    tmp = GETHQVSHADOW(shadow, 0x3D8);
    tmp |= (19 << 27);
    SETHQVSHADOW(shadow, 0x3D8, tmp);
} 
 
static void 
setHQVStartAddress(HQVRegister *shadow, unsigned yOffs, unsigned uOffs,
		   unsigned stride, unsigned format)
{
    CARD32 tmp = GETHQVSHADOW(shadow, 0x3D4);
    
    tmp |= yOffs & 0x03FFFFF0;
    SETHQVSHADOW(shadow, 0x3D4, tmp);
    tmp = GETHQVSHADOW(shadow, 0x3D8);
    tmp |= uOffs & 0x03FFFFF0;
    SETHQVSHADOW(shadow, 0x3D8, tmp);
    tmp = GETHQVSHADOW(shadow, 0x3F8);
    tmp |= (stride & 0x1FF8);
    SETHQVSHADOW(shadow, 0x3F8, tmp);
    tmp = GETHQVSHADOW(shadow, 0x3D0);

    if (format == 0) {
	/*
	 * NV12
	 */ 
	tmp |= (0x0C << 28);
    } else if (format == 1) {
	/*
	 * RGB16
	 */
	tmp |= (0x02 << 28);
    } else if (format == 2) {
	/*
	 * RGB32
	 */
	;
    }
    SETHQVSHADOW(shadow, 0x3D0, tmp);
}

static void
setHQVDeinterlacing(HQVRegister *shadow, CARD32 frameType) 
{
    CARD32 tmp = GETHQVSHADOW(shadow, 0x3D0);
 

    if ((frameType & XVMC_FRAME_PICTURE) == XVMC_TOP_FIELD) {
	tmp |= HQV_FIELD_UV   |
	    HQV_DEINTERLACE   |
	    HQV_FIELD_2_FRAME |
	    HQV_FRAME_2_FIELD;
    } else if ((frameType & XVMC_FRAME_PICTURE) == XVMC_BOTTOM_FIELD) {
	tmp |= HQV_FIELD_UV   |
	    HQV_DEINTERLACE   |
	    HQV_FIELD_2_FRAME |
	    HQV_FRAME_2_FIELD |
	    HQV_FLIP_ODD;
    } 
    SETHQVSHADOW(shadow, 0x3D0, tmp);
}

static void
setHQVTripleBuffer(HQVRegister *shadow, Bool on) 
{
    CARD32 tmp = GETHQVSHADOW(shadow, 0x3D0);

    if (on)
	tmp |= ( 1 << 26 );
    else 
	tmp &= ~( 1 << 26 );
    SETHQVSHADOW(shadow, 0x3D0, tmp);
}

static void
finish_header_agp(ViaCommandBuffer *cb) 
{
    int
      numDWords,i;
    CARD32
	*hb;

    if (!cb->mode) return;
    numDWords = cb->pos - cb->header_start - 4; 
    hb = cb->buf + cb->header_start;
    switch (cb->mode) {
    case VIA_AGP_HEADER5:
	hb[0] = VIA_AGP_HEADER5 | cb->rindex;
	hb[1] = numDWords ;
	hb[2] = 0x00F50000; /* SW debug flag. (?) */
	break;
    default:
	hb[0] = VIA_AGP_HEADER6;
	hb[1] = numDWords >> 1 ;
	hb[2] = 0x00F60000; /* SW debug flag. (?) */
	break;
    }
    hb[3] = 0;
    if (numDWords & 3) {
	for (i=0; i<(4 - (numDWords & 3)); ++i) 
	    OUT_RING_AGP(cb, 0x00000000);
    }
    cb->mode = 0;
}

void 
hwlLock(void *xlp, int videoLock) 
{
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;

    LL_HW_LOCK(xl);
}

void 
hwlUnlock(void *xlp, int videoLock) 
{
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;

    LL_HW_UNLOCK(xl);
}
    
static unsigned 
timeDiff(struct timeval *now,struct timeval *then) {
    return (now->tv_usec >= then->tv_usec) ?
	now->tv_usec - then->tv_usec : 
	1000000 - (then->tv_usec - now->tv_usec);
}

void 
setAGPSyncLowLevel(void *xlp, int val, CARD32 timeStamp)
{
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;

    xl->agpSync = val;
    xl->agpSyncTimeStamp = timeStamp;
}

CARD32 
viaDMATimeStampLowLevel(void *xlp)
{
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;

    if (xl->use_agp) {
	viaBlit(xl, 32, xl->tsOffset, 1, xl->tsOffset, 1, 1, 1, 0, 0, 
		VIABLIT_FILL, xl->curTimeStamp);
	return xl->curTimeStamp++;
    }
    return 0;
}

static void 
viaDMAWaitTimeStamp(XvMCLowLevel *xl, CARD32 timeStamp, int doSleep) 
{
    struct timeval now, then;
    struct timezone here;
    struct timespec sleep, rem;

    if (xl->use_agp && (xl->lastReadTimeStamp - timeStamp > (1 << 23))) {
	sleep.tv_nsec = 1;
	sleep.tv_sec = 0;
	here.tz_minuteswest = 0;
	here.tz_dsttime = 0;
	gettimeofday(&then,&here);
 
	while(((xl->lastReadTimeStamp = *xl->tsP) - timeStamp) > (1 << 23)) {
	    gettimeofday(&now,&here); 
	    if (timeDiff(&now,&then) > VIA_DMAWAITTIMEOUT) {
		if(((xl->lastReadTimeStamp = *xl->tsP) - timeStamp) > (1 << 23)) {
		    xl->errors |= LL_DMA_TIMEDOUT;
		    break;
		}
	    }
	    if (doSleep) nanosleep(&sleep, &rem);
	}
    }
}

static int 
viaDMAInitTimeStamp(XvMCLowLevel *xl) 
{
    int ret = 0;

    if (xl->use_agp) {
	xl->tsMem.context = *(xl->drmcontext);
	xl->tsMem.size = 64;
	xl->tsMem.type = VIA_MEM_VIDEO;
	if ((ret = drmCommandWriteRead(xl->fd, DRM_VIA_ALLOCMEM, 
				       &xl->tsMem, sizeof(xl->tsMem))) < 0) 
	    return ret;
	if (xl->tsMem.size != 64)
	    return -1;
	xl->tsOffset = (xl->tsMem.offset + 31) & ~31; 
	xl->tsP = (CARD32 *)xl->fbAddress + (xl->tsOffset >> 2); 
	xl->curTimeStamp = 1;
	*xl->tsP = 0;
    }
    return 0;
}

static int 
viaDMACleanupTimeStamp(XvMCLowLevel *xl) 
{

    if (!(xl->tsMem.size) || !xl->use_agp) return 0;
    return drmCommandWrite(xl->fd, DRM_VIA_FREEMEM, &xl->tsMem, sizeof(xl->tsMem)); 
}


static CARD32 
viaMpegGetStatus(XvMCLowLevel *xl)
{
    return MPEGIN(xl,0x54);
}

static int 
viaMpegIsBusy(XvMCLowLevel *xl, CARD32 mask, CARD32 idle)
{
    CARD32 tmp = viaMpegGetStatus(xl);

    /*
     * Error detected. 
     * FIXME: Are errors really shown when error concealment is on?
     */

    if (tmp & 0x70) return 0; 

    return (tmp & mask) != idle;
}


static void 
syncDMA(XvMCLowLevel *xl, unsigned int doSleep)
{
  
    /*
     * Ideally, we'd like to have an interrupt wait here, but, according to second hand
     * information, the hardware does not support this, although earlier S3 chips do that.
     * It is therefore not implemented into the DRM, and we'll do a user space wait here.
     */

    struct timeval now, then;
    struct timezone here;
    struct timespec sleep, rem;

    sleep.tv_nsec = 1;
    sleep.tv_sec = 0;
    here.tz_minuteswest = 0;
    here.tz_dsttime = 0;
    gettimeofday(&then,&here);
    while( !(REGIN(xl, VIA_REG_STATUS) & VIA_VR_QUEUE_BUSY)) {
	gettimeofday(&now,&here); 
	if (timeDiff(&now,&then) > VIA_DMAWAITTIMEOUT) {
	    if( !(REGIN(xl, VIA_REG_STATUS) & VIA_VR_QUEUE_BUSY)) {
		xl->errors |= LL_DMA_TIMEDOUT;
		break;
	    }
	}
	if (doSleep) nanosleep(&sleep, &rem);
    }
    while( REGIN(xl, VIA_REG_STATUS) & VIA_CMD_RGTR_BUSY ) {
	gettimeofday(&now,&here); 
	if (timeDiff(&now,&then) > VIA_DMAWAITTIMEOUT) {
	    if( REGIN(xl, VIA_REG_STATUS) & VIA_CMD_RGTR_BUSY ) {
		xl->errors |= LL_DMA_TIMEDOUT;
		break;
	    }
	}
	if (doSleep) nanosleep(&sleep, &rem);
    }
}

#ifdef HQV_USE_IRQ
static void 
syncVideo(XvMCLowLevel *xl, unsigned int doSleep)
{
    int proReg = REG_HQV1_INDEX;

    /*
     * Wait for HQV completion using completion interrupt. Nothing strange here. 
     * Note that the interrupt handler clears the HQV_FLIP_STATUS bit, so we 
     * can't wait on that one.
     */
    
    if ((VIDIN(xl, HQV_CONTROL|proReg) & (HQV_SW_FLIP | HQV_SUBPIC_FLIP))) {
	drm_via_irqwait_t irqw;
	irqw.request.irq = 1;
	irqw.request.type = VIA_IRQ_ABSOLUTE;
	if (drmCommandWriteRead(xl->fd, DRM_VIA_WAIT_IRQ, &irqw, sizeof(irqw)) < 0) 
	    xl->errors |= LL_VIDEO_TIMEDOUT;
    }
}
#else
static void 
syncVideo(XvMCLowLevel *xl, unsigned int doSleep)
{
    /*
     * Wait for HQV completion. Nothing strange here. We assume that the HQV
     * Handles syncing to the V1 / V3 engines by itself. It should be safe to
     * always wait for SUBPIC_FLIP completion although subpictures are not always
     * used. 
     */
    
    struct timeval now, then;
    struct timezone here;
    struct timespec sleep, rem;

    int proReg = REG_HQV1_INDEX;

    sleep.tv_nsec = 1;
    sleep.tv_sec = 0;
    here.tz_minuteswest = 0;
    here.tz_dsttime = 0;
    gettimeofday(&then,&here); 
    while((VIDIN(xl, HQV_CONTROL|proReg) & (HQV_SW_FLIP | HQV_SUBPIC_FLIP  )) ) {
	gettimeofday(&now,&here); 
	if (timeDiff(&now,&then) > VIA_SYNCWAITTIMEOUT) {
	  if((VIDIN(xl, HQV_CONTROL|proReg) & (HQV_SW_FLIP | HQV_SUBPIC_FLIP )) ) {
		xl->errors |= LL_VIDEO_TIMEDOUT;
		break;
	    }
	}
	if (doSleep) nanosleep(&sleep, &rem);
    }
}
#endif

static void 
syncAccel(XvMCLowLevel *xl, unsigned int mode, unsigned int doSleep)
{
    struct timeval now, then;
    struct timezone here;
    struct timespec sleep, rem;
    CARD32 mask = ((mode & LL_MODE_2D) ? VIA_2D_ENG_BUSY : 0) |
	((mode & LL_MODE_3D) ? VIA_3D_ENG_BUSY : 0);

    sleep.tv_nsec = 1;
    sleep.tv_sec = 0;
    here.tz_minuteswest = 0;
    here.tz_dsttime = 0;
    gettimeofday(&then,&here); 
    while( REGIN(xl, VIA_REG_STATUS) & mask) {
	gettimeofday(&now,&here); 
	if (timeDiff(&now,&then) > VIA_SYNCWAITTIMEOUT) {
	    if( REGIN(xl, VIA_REG_STATUS) & mask) {
		xl->errors |= LL_ACCEL_TIMEDOUT;
		break;
	    }
	}
	if (doSleep) nanosleep(&sleep, &rem);
    }
}


static void 
syncMpeg(XvMCLowLevel *xl, unsigned int mode, unsigned int doSleep)
{
    /*
     * Ideally, we'd like to have an interrupt wait here, but from information from VIA
     * at least the MPEG completion interrupt is broken on the CLE266, which was
     * discovered during validation of the chip.
     */

    struct timeval now, then;
    struct timezone here;
    struct timespec sleep, rem;
    CARD32 busyMask = 0;
    CARD32 idleVal = 0;
    CARD32 ret;

    sleep.tv_nsec = 1;
    sleep.tv_sec = 0;
    here.tz_minuteswest = 0;
    here.tz_dsttime = 0;
    gettimeofday(&then,&here); 
    if (mode & LL_MODE_DECODER_SLICE) {
	busyMask = VIA_SLICEBUSYMASK;
	idleVal = VIA_SLICEIDLEVAL;
    }
    if (mode & LL_MODE_DECODER_IDLE) {
	busyMask |= VIA_BUSYMASK;
	idleVal = VIA_IDLEVAL;
    }
    while(viaMpegIsBusy(xl, busyMask, idleVal)) {
	gettimeofday(&now,&here); 
	if (timeDiff(&now,&then) > VIA_XVMC_DECODERTIMEOUT) {
	    if (viaMpegIsBusy(xl, busyMask, idleVal)) {
		xl->errors |= LL_DECODER_TIMEDOUT;
	    }
	    break;
	}
	if (doSleep) nanosleep(&sleep, &rem);
    }
  
    ret = viaMpegGetStatus(xl);
    if (ret & 0x70) {
	xl->errors |= ((ret & 0x70) >> 3);
    }
    return;
}

static void 
pciFlush(ViaCommandBuffer *cb, XvMCLowLevel *xl)
{
    int ret;
    drm_via_cmdbuffer_t b;
    unsigned mode = cb->waitFlags;
  
    finish_header_agp(cb);
    b.buf = (char *)cb->buf;
    b.size = cb->pos * sizeof(CARD32);
    if (xl->performLocking) hwlLock(xl,0);
    if (((mode == LL_MODE_VIDEO) && (xl->videoBuf == &xl->agpBuf)) ||
	((mode != LL_MODE_VIDEO) && (mode != 0)))
	syncDMA(xl, 0);
    if ((mode & LL_MODE_2D) || (mode & LL_MODE_3D)) {
      syncAccel(xl, mode, 0); 
    }
    if (mode & LL_MODE_VIDEO) {
      syncVideo(xl, 1);
    }
    if (mode & (LL_MODE_DECODER_SLICE | LL_MODE_DECODER_IDLE)) {
      syncMpeg(xl, mode, 0); 
    }
    ret = drmCommandWrite(xl->fd, DRM_VIA_PCICMD, &b, sizeof(b));
    if (xl->performLocking) hwlUnlock(xl,0);
    if (ret) {
	xl->errors |= LL_PCI_COMMAND_ERR;
    }
    cb->pos = 0;
    cb->waitFlags = 0;
}
  
static void 
agpFlush(ViaCommandBuffer *cb, XvMCLowLevel *xl)
{
    drm_via_cmdbuffer_t b;
    int ret;
    int i;

    finish_header_agp(cb);
    if (xl->use_agp) {
	b.buf = (char *)cb->buf;
	b.size = cb->pos * sizeof(CARD32);
	if (xl->agpSync) {
	    syncXvMCLowLevel(xl, LL_MODE_DECODER_IDLE, 1, xl->agpSyncTimeStamp);
	    xl->agpSync = 0;
	}
	if (xl->performLocking) hwlLock(xl,0);
	do {
	    ret = drmCommandWrite(xl->fd, DRM_VIA_CMDBUFFER, &b, sizeof(b));
	} while (-EAGAIN == ret);
	if (xl->performLocking) hwlUnlock(xl,0);
      
	if (ret) {
	    xl->errors |= LL_AGP_COMMAND_ERR;
	    for(i=0; i<cb->pos; i+=2) {
	      printf("0x%x, 0x%x\n", (unsigned) cb->buf[i], (unsigned) cb->buf[i+1]);
	    }
	    exit(-1);
	} else {
	    cb->pos = 0;
	}
	cb->waitFlags &= LL_MODE_VIDEO; /* FIXME: Check this! */
    } else {
	unsigned mode=cb->waitFlags;
  
	b.buf = (char *)cb->buf;
	b.size = cb->pos * sizeof(CARD32);
	if (xl->performLocking) hwlLock(xl,0);
	if (((mode == LL_MODE_VIDEO) && (cb == &xl->agpBuf)) ||
	    ((mode != LL_MODE_VIDEO) && (mode != 0)))
	    syncDMA(xl, 0);
	if ((mode & LL_MODE_2D) || (mode & LL_MODE_3D)) 
	    syncAccel(xl, mode, 0);
	if (mode & LL_MODE_VIDEO) 
	    syncVideo(xl, 1);
	if (mode & (LL_MODE_DECODER_SLICE | LL_MODE_DECODER_IDLE))
	    syncMpeg(xl, mode, 0);
	ret = drmCommandWrite(xl->fd, DRM_VIA_PCICMD, &b, sizeof(b));
	if (xl->performLocking) hwlUnlock(xl,0);
	if (ret) {
	    xl->errors |= LL_PCI_COMMAND_ERR;
	}
	cb->pos = 0;
	cb->waitFlags = 0;
    }      
}

static void
uploadHQVShadow(XvMCLowLevel *xl, unsigned offset, HQVRegister *shadow,
		Bool flip)
{
    int i;
    CARD32 tmp;
    ViaCommandBuffer *cb = xl->videoBuf;

    BEGIN_HEADER6_DATA(cb, xl, HQV_SHADOW_SIZE);
    WAITFLAGS(cb, LL_MODE_VIDEO);

    if (shadow[0].set)
	OUT_RING_QW_AGP(cb, 0x3CC + offset, 0);
    
    for (i=2; i < HQV_SHADOW_SIZE; ++i) {
      if (shadow[i].set) {
	  OUT_RING_QW_AGP(cb, offset + HQV_SHADOW_BASE + ( i << 2) , shadow[i].data);  
	  shadow[i].set = FALSE;
      }
    }

    /*
     * Finally the control register for flip.
     */
    
    if (flip) {
	tmp = GETHQVSHADOW( shadow, 0x3D0);
        OUT_RING_QW_AGP(cb, offset + HQV_CONTROL + 0x200 , 
			HQV_ENABLE | HQV_GEN_IRQ | HQV_SUBPIC_FLIP | HQV_SW_FLIP | tmp); 
    }
    shadow[0].set = FALSE;
    shadow[1].set = FALSE;
}
    



unsigned 
flushXvMCLowLevel(void *xlp) 
{
    unsigned 
	errors;
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;


    if(xl->pciBuf.pos) pciFlush(&xl->pciBuf, xl);
    if(xl->agpBuf.pos) agpFlush(&xl->agpBuf, xl);
    errors = xl->errors;
    if (errors) printf("Error 0x%x\n", errors);
    xl->errors = 0;
    return errors;
}

void 
flushPCIXvMCLowLevel(void *xlp) 
{
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;


    if(xl->pciBuf.pos) pciFlush(&xl->pciBuf, xl); 
    if ((!xl->use_agp &&  xl->agpBuf.pos)) agpFlush(&xl->agpBuf, xl);
}


void 
viaMpegSetSurfaceStride(void *xlp, ViaXvMCContext *ctx)
{
    CARD32 y_stride = ctx->yStride;
    CARD32 uv_stride = y_stride >> 1;
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;    
    ViaCommandBuffer *cb = &xl->agpBuf;

    BEGIN_HEADER6_DATA(cb, xl, 1);
    OUT_RING_QW_AGP(cb, 0xc50, (y_stride >> 3) | ((uv_stride >> 3) << 16)); 
    WAITFLAGS(cb, LL_MODE_DECODER_IDLE);
}


void 
viaVideoSetSWFLipLocked(void *xlp, unsigned yOffs, unsigned uOffs,
			unsigned vOffs, unsigned yStride, unsigned uvStride) 
{
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;

    initHQVShadow(hqvShadow);
    setHQVStartAddress(hqvShadow, yOffs, vOffs, yStride, 0);
    if (xl->videoBuf == &xl->agpBuf)
	syncDMA(xl, 1);
    syncVideo(xl, 1);
    uploadHQVShadow(xl, REG_HQV1_INDEX, hqvShadow, FALSE);
    xl->videoBuf->flushFunc(xl->videoBuf, xl);
}
    
void 
viaVideoSWFlipLocked(void *xlp, unsigned flags,
		     Bool progressiveSequence)
{
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;

    setHQVDeinterlacing(hqvShadow, flags);
    setHQVDeblocking(hqvShadow,( (flags & XVMC_FRAME_PICTURE) == XVMC_FRAME_PICTURE), TRUE);
    setHQVTripleBuffer(hqvShadow, TRUE);
    if (xl->videoBuf == &xl->agpBuf)
	syncDMA(xl, 1);
    syncVideo(xl, 1);
    uploadHQVShadow(xl, REG_HQV1_INDEX, hqvShadow, TRUE);    
    xl->videoBuf->flushFunc(xl->videoBuf, xl);
}

	
void 
viaMpegSetFB(void *xlp,unsigned i,
	     unsigned yOffs,
	     unsigned uOffs,
	     unsigned vOffs) 
{
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;
    ViaCommandBuffer *cb = &xl->agpBuf;

    i *= (4*2);
    BEGIN_HEADER6_DATA(cb,xl, 2);
    OUT_RING_QW_AGP(cb, 0xc28 + i, yOffs >> 3);
    OUT_RING_QW_AGP(cb, 0xc2c + i, vOffs >> 3);

    WAITFLAGS(cb, LL_MODE_DECODER_IDLE);
}

void 
viaMpegBeginPicture(void *xlp,ViaXvMCContext *ctx,
		    unsigned width,
		    unsigned height,
		    const XvMCMpegControl *control) {
				  
    unsigned j, mb_width, mb_height;
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;
    ViaCommandBuffer *cb = &xl->agpBuf;

    mb_width = (width + 15) >> 4;

    mb_height =
	((control->mpeg_coding == XVMC_MPEG_2) && 
	 (control->flags & XVMC_PROGRESSIVE_SEQUENCE)) ?
	2*((height+31) >> 5) : (((height+15) >> 4));

    BEGIN_HEADER6_DATA(cb,xl, 72);
    WAITFLAGS(cb, LL_MODE_DECODER_IDLE);

    OUT_RING_QW_AGP(cb, 0xc00,
		    ((control->picture_structure & XVMC_FRAME_PICTURE) << 2) |
		    ((control->picture_coding_type & 3) << 4) |
		    ((control->flags & XVMC_ALTERNATE_SCAN) ? (1 << 6) : 0));
   
    if (!(ctx->intraLoaded)) {
	OUT_RING_QW_AGP(cb, 0xc5c, 0);
	for (j = 0; j < 64; j += 4) {
            OUT_RING_QW_AGP(cb, 0xc60,  
			    ctx->intra_quantiser_matrix[j] |
			    (ctx->intra_quantiser_matrix[j+1] << 8) |
			    (ctx->intra_quantiser_matrix[j+2] << 16) |
			    (ctx->intra_quantiser_matrix[j+3] << 24));
        }
	ctx->intraLoaded = 1;
    }

    if (!(ctx->nonIntraLoaded)) {
	OUT_RING_QW_AGP(cb, 0xc5c, 1);
	for (j = 0; j < 64; j += 4) {
            OUT_RING_QW_AGP(cb, 0xc60,  
			    ctx->non_intra_quantiser_matrix[j] |
			    (ctx->non_intra_quantiser_matrix[j+1] << 8) |
			    (ctx->non_intra_quantiser_matrix[j+2] << 16) |
			    (ctx->non_intra_quantiser_matrix[j+3] << 24));
        }
	ctx->nonIntraLoaded = 1;
    }

    if (!(ctx->chromaIntraLoaded)) {
	OUT_RING_QW_AGP(cb, 0xc5c, 2);
	for (j = 0; j < 64; j += 4) {
            OUT_RING_QW_AGP(cb, 0xc60,  
			    ctx->chroma_intra_quantiser_matrix[j] |
			    (ctx->chroma_intra_quantiser_matrix[j+1] << 8) |
			    (ctx->chroma_intra_quantiser_matrix[j+2] << 16) |
			    (ctx->chroma_intra_quantiser_matrix[j+3] << 24));
        }
	ctx->chromaIntraLoaded = 1;
    }

    if (!(ctx->chromaNonIntraLoaded)) {
	OUT_RING_QW_AGP(cb, 0xc5c, 3);
	for (j = 0; j < 64; j += 4) {
            OUT_RING_QW_AGP(cb, 0xc60,  
			    ctx->chroma_non_intra_quantiser_matrix[j] |
			    (ctx->chroma_non_intra_quantiser_matrix[j+1] << 8) |
			    (ctx->chroma_non_intra_quantiser_matrix[j+2] << 16) |
			    (ctx->chroma_non_intra_quantiser_matrix[j+3] << 24));
        }
	ctx->chromaNonIntraLoaded = 1;
    }
    
    OUT_RING_QW_AGP(cb, 0xc90,  
		    ((mb_width * mb_height) & 0x3fff) |
		    ((control->flags & XVMC_PRED_DCT_FRAME) ? ( 1 << 14)  : 0) |
		    ((control->flags & XVMC_TOP_FIELD_FIRST) ? (1 << 15) : 0 )  |
		    ((control->mpeg_coding == XVMC_MPEG_2) ? (1 << 16) : 0) |
		    ((mb_width & 0xff) << 18));
    	
    OUT_RING_QW_AGP(cb, 0xc94,
		    ((control->flags & XVMC_CONCEALMENT_MOTION_VECTORS) ? 1 : 0) |
		    ((control->flags & XVMC_Q_SCALE_TYPE) ? 2 : 0) |
		    ((control->intra_dc_precision & 3) << 2) |
		    (((1 + 0x100000 / mb_width) & 0xfffff) << 4) |
		    ((control->flags & XVMC_INTRA_VLC_FORMAT) ? (1 << 24) : 0));

    OUT_RING_QW_AGP(cb, 0xc98, 
		    (((control->FHMV_range) & 0xf) << 0) |
		    (((control->FVMV_range) & 0xf) << 4) |
		    (((control->BHMV_range) & 0xf) << 8) |
		    (((control->BVMV_range) & 0xf) << 12) |
		    ((control->flags & XVMC_SECOND_FIELD) ?  (1 << 20) : 0) | 
		    (0x0a6 << 16));

}



void 
viaMpegReset(void *xlp)
{
    int i,j;
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;
    ViaCommandBuffer *cb = &xl->agpBuf;

    BEGIN_HEADER6_DATA(cb,xl, 99);
    WAITFLAGS(cb, LL_MODE_DECODER_IDLE);

    OUT_RING_QW_AGP(cb, 0xcf0 ,0);

    for (i = 0; i < 6; i++) {
        OUT_RING_QW_AGP(cb, 0xcc0 ,0);
        OUT_RING_QW_AGP(cb, 0xc0c, 0x43|0x20 );
        for (j = 0xc10; j < 0xc20; j += 4)
	        OUT_RING_QW_AGP(cb, j, 0);
    }

    OUT_RING_QW_AGP(cb, 0xc0c, 0x1c3);
    for (j = 0xc10; j < 0xc20; j += 4)
	    OUT_RING_QW_AGP(cb,j,0);

    for (i = 0; i < 19; i++)
        OUT_RING_QW_AGP(cb, 0xc08 ,0);
    
    OUT_RING_QW_AGP(cb, 0xc98, 0x400000);

    for (i = 0; i < 6; i++) {
    OUT_RING_QW_AGP(cb, 0xcc0 ,0);
    OUT_RING_QW_AGP(cb, 0xc0c, 0x1c3|0x20);
    for (j = 0xc10; j < 0xc20; j += 4)
	    OUT_RING_QW_AGP(cb,j,0);
    }
    OUT_RING_QW_AGP(cb, 0xcf0 ,0);

}

void 
viaMpegWriteSlice(void *xlp, CARD8* slice, int nBytes, CARD32 sCode)
{
    int i, n, r;
    CARD32* buf;
    int count;
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;
    ViaCommandBuffer *cb = &xl->agpBuf;

    if (xl->errors & (LL_DECODER_TIMEDOUT |
		      LL_IDCT_FIFO_ERROR  |
		      LL_SLICE_FIFO_ERROR |
		      LL_SLICE_FAULT)) return;

    n = nBytes >> 2;
    if (sCode) nBytes += 4;
    r = nBytes & 3;
    buf = (CARD32*) slice;

    if (r) nBytes += 4 - r;

    nBytes += 8;

    BEGIN_HEADER6_DATA(cb,xl, 2);
    WAITFLAGS(cb, LL_MODE_DECODER_IDLE);
    OUT_RING_QW_AGP(cb, 0xc9c, nBytes);
    
    if (sCode) OUT_RING_QW_AGP(cb, 0xca0, sCode);

    i = 0;
    count = 0;

    do {
	count += (LL_AGP_CMDBUF_SIZE -20);
	count = (count > n) ? n : count;
	BEGIN_HEADER5_DATA(cb, xl, (count - i), 0xca0);

	for (; i < count; i++) {
	    OUT_RING_AGP(cb, *buf++);
	}
	finish_header_agp(cb);
    } while (i < n);

    BEGIN_HEADER5_DATA(cb, xl, 3, 0xca0);

    if (r) {
	OUT_RING_AGP(cb, *buf & ((1 << (r << 3)) - 1));
    }
    OUT_RING_AGP(cb,0);
    OUT_RING_AGP(cb,0);
    finish_header_agp(cb);
}

void 
viaVideoSubPictureOffLocked(void *xlp) {

    CARD32 stride;
    int proReg = REG_HQV1_INDEX;
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;
    ViaCommandBuffer *cb = xl->videoBuf;

    if (xl->videoBuf == &xl->agpBuf)
	syncDMA(xl, 1);
    stride = VIDIN(xl,proReg|SUBP_CONTROL_STRIDE);
    WAITFLAGS(cb, LL_MODE_VIDEO);
    BEGIN_HEADER6_DATA(cb, xl, 1);
    OUT_RING_QW_AGP(cb, proReg|SUBP_CONTROL_STRIDE | 0x200, stride & ~SUBP_HQV_ENABLE);
}

void 
viaVideoSubPictureLocked(void *xlp, ViaXvMCSubPicture *pViaSubPic) {

    unsigned i;
    CARD32 cWord;    
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;
    int proReg = REG_HQV1_INDEX;    
    ViaCommandBuffer *cb = xl->videoBuf;

    if (xl->videoBuf == &xl->agpBuf)
	syncDMA(xl, 1);
    WAITFLAGS(cb, LL_MODE_VIDEO);
    BEGIN_HEADER6_DATA(cb, xl, VIA_SUBPIC_PALETTE_SIZE + 2);
    for (i=0; i<VIA_SUBPIC_PALETTE_SIZE; ++i) {
	OUT_RING_QW_AGP(cb, proReg|RAM_TABLE_CONTROL | 0x200, pViaSubPic->palette[i]);
    }

    cWord = (pViaSubPic->stride & SUBP_STRIDE_MASK) | SUBP_HQV_ENABLE;
    cWord |= (pViaSubPic->ia44) ? SUBP_IA44 : SUBP_AI44;
    OUT_RING_QW_AGP(cb, proReg|SUBP_STARTADDR | 0x200, pViaSubPic->offset);
    OUT_RING_QW_AGP(cb, proReg|SUBP_CONTROL_STRIDE | 0x200, cWord);
}

void 
viaBlit(void *xlp,unsigned bpp,unsigned srcBase,
	unsigned srcPitch,unsigned dstBase,unsigned dstPitch,
	unsigned w,unsigned h,int xdir,int ydir, unsigned blitMode, 
	unsigned color) 
{

    CARD32 dwGEMode = 0, srcY=0, srcX, dstY=0, dstX;
    CARD32 cmd;
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;
    ViaCommandBuffer *cb = &xl->agpBuf;

    if (!w || !h)
        return;

    finish_header_agp(cb);

    switch (bpp) {
    case 16:
        dwGEMode |= VIA_GEM_16bpp;
        break;
    case 32:
        dwGEMode |= VIA_GEM_32bpp;
	break;
    default:
        dwGEMode |= VIA_GEM_8bpp;
        break;
    }

    srcX = srcBase & 31;
    dstX = dstBase & 31;
    switch (bpp) {
    case 16:
        dwGEMode |= VIA_GEM_16bpp;
	srcX >>= 2;
	dstX >>= 2;
        break;
    case 32:
        dwGEMode |= VIA_GEM_32bpp;
	srcX >>= 4;
	dstX >>= 4;
	break;
    default:
        dwGEMode |= VIA_GEM_8bpp;
        break;
    }

    BEGIN_RING_AGP(cb, xl, 20);
    WAITFLAGS(cb, LL_MODE_2D);


    OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_GEMODE), dwGEMode);
    cmd = 0; 

    if (xdir < 0) {
        cmd |= VIA_GEC_DECX;
        srcX += (w - 1);
        dstX += (w - 1);
    }
    if (ydir < 0) {
        cmd |= VIA_GEC_DECY;
        srcY += (h - 1);
        dstY += (h - 1);
    }

    switch(blitMode) {
    case VIABLIT_TRANSCOPY:
	OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_SRCCOLORKEY), color);
	OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_KEYCONTROL), 0x4000);
	cmd |= VIA_GEC_BLT | (VIA_BLIT_COPY << 24);
	break;
    case VIABLIT_FILL:
	OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_FGCOLOR), color);
	cmd |= VIA_GEC_BLT | VIA_GEC_FIXCOLOR_PAT | (VIA_BLIT_FILL << 24);
	break;
    default:
	OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_KEYCONTROL), 0x0);
	cmd |= VIA_GEC_BLT | (VIA_BLIT_COPY << 24);
    }	

    OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_SRCBASE), (srcBase & ~31) >> 3);
    OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_DSTBASE), (dstBase & ~31) >> 3);
    OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_PITCH), VIA_PITCH_ENABLE |
		    (srcPitch >> 3) | (((dstPitch) >> 3) << 16));
    OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_SRCPOS), ((srcY << 16) | srcX));
    OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_DSTPOS), ((dstY << 16) | dstX));
    OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_DIMENSION), (((h - 1) << 16) | (w - 1)));
    OUT_RING_QW_AGP(cb, H1_ADDR(VIA_REG_GECMD), cmd);
}

unsigned 
syncXvMCLowLevel(void *xlp, unsigned int mode, unsigned int doSleep,
		 CARD32 timeStamp)
{
    unsigned
	errors;
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;

    if (mode == 0) {
	errors = xl->errors;
	xl->errors = 0;
	return errors;
    }

    if ((mode & (LL_MODE_VIDEO | LL_MODE_3D)) || !xl->use_agp) {
	if (xl->performLocking) 
	    hwlLock(xl,0);	
	if ((xl->videoBuf == &xl->agpBuf) ||  (mode != LL_MODE_VIDEO))
	    syncDMA(xl, doSleep);
	if (mode & LL_MODE_3D) 
	    syncAccel(xl, mode, doSleep);
	if (mode & LL_MODE_VIDEO)
	    syncVideo(xl, doSleep);
	if (xl->performLocking) 
	    hwlUnlock(xl,0);
    } else {
	viaDMAWaitTimeStamp(xl, timeStamp, doSleep);
    }

    if (mode & (LL_MODE_DECODER_SLICE | LL_MODE_DECODER_IDLE)) 
	syncMpeg(xl, mode, doSleep);

    errors = xl->errors;
    xl->errors = 0;

    return errors;
}

static int
updateLowLevelBuf(XvMCLowLevel *xl, LowLevelBuffer *buf, 
		  unsigned width, unsigned height)
{
    unsigned
	stride, size;
    drm_via_mem_t *mem = &buf->mem;
    int ret;

    stride = (width + 31) & ~31;
    size = stride * height + (xl->fbDepth >> 3);
    
    if (size != mem->size) {
	if (mem->size) 
	    drmCommandWrite(xl->fd, DRM_VIA_FREEMEM, mem, sizeof(*mem)); 
	mem->context = *(xl->drmcontext);
	mem->size = size;
	mem->type = VIA_MEM_VIDEO;
	
	if (((ret = drmCommandWriteRead(xl->fd, DRM_VIA_ALLOCMEM, mem, sizeof(mem))) < 0)  ||
	    mem->size != size) {
	    mem->size = 0;
	    return -1;	
	}    
    }
    
    buf->offset = (mem->offset + 31) & ~31;
    buf->stride = stride;
    buf->height = height;
    return 0;
}

static void
cleanupLowLevelBuf(XvMCLowLevel *xl, LowLevelBuffer *buf)
{
    drm_via_mem_t *mem = &buf->mem;

    if (mem->size) 
	drmCommandWrite(xl->fd, DRM_VIA_FREEMEM, mem, sizeof(mem)); 
    mem->size = 0;
}
    

static void
*releaseXvMCLowLevel(XvMCLowLevel *xl)
{
    switch(xl->state) {
    case ll_llBuf:
	cleanupLowLevelBuf(xl, &xl->scale);
    case ll_timeStamp:
	viaDMACleanupTimeStamp(xl);
    case ll_pciBuf:
	free(xl->pciBuf.buf);
    case ll_agpBuf:
	free(xl->agpBuf.buf);
    case ll_init:
	free(xl);
    default:
	;
    }
    return NULL;
}
	
	
void 
*initXvMCLowLevel(int fd, drm_context_t *ctx,
		  drmLockPtr hwLock, drmAddress mmioAddress, 
		  drmAddress fbAddress, unsigned fbStride, unsigned fbDepth,
		  unsigned width, unsigned height, int useAgp, unsigned chipId ) 
{
    XvMCLowLevel *xl = (XvMCLowLevel *)malloc(sizeof(XvMCLowLevel));

    if (!xl) return NULL;
    xl->state = ll_init;

    xl->agpBuf.buf = (CARD32 *)malloc(LL_AGP_CMDBUF_SIZE * sizeof(CARD32));
    if (!xl->agpBuf.buf) return releaseXvMCLowLevel(xl);
    xl->state = ll_agpBuf;
    xl->agpBuf.bufSize = LL_AGP_CMDBUF_SIZE;
    xl->agpBuf.flushFunc = &agpFlush;
    xl->agpBuf.pos = 0;
    xl->agpBuf.mode = 0;
    xl->agpBuf.waitFlags = 0;

    xl->pciBuf.buf = (CARD32 *)malloc(LL_PCI_CMDBUF_SIZE * sizeof(CARD32));
    if (!xl->pciBuf.buf) return releaseXvMCLowLevel(xl);
    xl->state = ll_pciBuf;
    xl->pciBuf.bufSize = LL_PCI_CMDBUF_SIZE;
    xl->pciBuf.flushFunc = &pciFlush;
    xl->pciBuf.pos = 0;
    xl->pciBuf.mode = 0;
    xl->pciBuf.waitFlags = 0;

    xl->use_agp = useAgp;
    xl->fd = fd;
    xl->drmcontext = ctx;
    xl->hwLock = hwLock;
    xl->mmioAddress = mmioAddress;
    xl->fbAddress = fbAddress;
    xl->fbDepth = fbDepth;
    xl->fbStride = fbStride;
    xl->width = width;
    xl->height = height;
    xl->performLocking = 1;
    xl->errors = 0;
    xl->agpSync = 0;
    xl->chipId = chipId;

    if (viaDMAInitTimeStamp(xl)) 
	return releaseXvMCLowLevel(xl);
    xl->state = ll_timeStamp;
    
    xl->scale.mem.size = 0;
    xl->back.mem.size = 0;

    if (updateLowLevelBuf(xl, &xl->scale, width, height)) 
	return releaseXvMCLowLevel(xl);
    xl->state = ll_llBuf;

#ifdef VIDEO_DMA
    xl->videoBuf = &xl->agpBuf;
#else
    xl->videoBuf = &xl->pciBuf;
#endif

    return xl;
}

void 
setLowLevelLocking(void *xlp, int performLocking)
{
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;
    xl->performLocking = performLocking;
}

void 
closeXvMCLowLevel(void *xlp) 
{
    XvMCLowLevel *xl = (XvMCLowLevel *) xlp;
    releaseXvMCLowLevel(xl);
}
