/*
 * Copyright © 2004 Eric Anholt
 *
 * 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 Eric Anholt not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Eric Anholt makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * ERIC ANHOLT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL ERIC ANHOLT 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.
 */
/* $Header$ */

#include <sys/time.h>

#include "ati.h"
#include "ati_reg.h"
#include "ati_dma.h"
#include "ati_draw.h"

#ifdef USE_DRI
#include "radeon_common.h"
#include "r128_common.h"
#include "ati_sarea.h"
#endif /* USE_DRI */

#include "agp.h"

#define DEBUG_FIFO 0

extern CARD32 r128_cce_microcode[];
extern CARD32 radeon_cp_microcode[][2];
extern CARD32 r200_cp_microcode[][2];
extern CARD32 r300_cp_microcode[][2];

static void
ATIDebugFifo(ATIScreenInfo *atis)
{
	ATICardInfo *atic = atis->atic;
	char *mmio = atic->reg_base;

	if (atic->is_radeon) {
		ErrorF("RADEON_REG_CP_CSQ_CNTL: 0x%08x\n",
		    MMIO_IN32(mmio, RADEON_REG_CP_CSQ_CNTL));
		ErrorF("RADEON_REG_CP_CSQ_STAT: 0x%08x\n",
		    MMIO_IN32(mmio, RADEON_REG_CP_CSQ_STAT));
		ErrorF("RADEON_REG_RBBM_STATUS: 0x%08x\n",
		    MMIO_IN32(mmio, RADEON_REG_RBBM_STATUS));
		ErrorF("RADEON_REG_RB3D_DSTCACHE_CTLSTAT: 0x%08x\n",
		    MMIO_IN32(mmio, RADEON_REG_RB3D_DSTCACHE_CTLSTAT));
	} else {
		ErrorF("R128_REG_PM4_BUFFER_CNTL: 0x%08x\n",
		    MMIO_IN32(mmio, R128_REG_PM4_BUFFER_CNTL));
		ErrorF("R128_REG_PM4_STAT: 0x%08x\n",
		    MMIO_IN32(mmio, R128_REG_PM4_STAT));
		ErrorF("R128_REG_GUI_STAT: 0x%08x\n",
		    MMIO_IN32(mmio, R128_REG_GUI_STAT));
		ErrorF("R128_REG_PC_NGUI_CTLSTAT: 0x%08x\n",
		    MMIO_IN32(mmio, R128_REG_PC_NGUI_CTLSTAT));
	}
}

static void
ATIUploadMicrocode(ATIScreenInfo *atis)
{
	ATICardInfo *atic = atis->atic;
	char *mmio = atic->reg_base;
	int i;

	MMIO_OUT32(mmio, ATI_REG_MICROCODE_RAM_ADDR, 0);
	if (atic->is_radeon && atic->is_r300) {
		for (i = 0; i < 256; i++) {
			MMIO_OUT32(mmio, ATI_REG_MICROCODE_RAM_DATAH,
			    r300_cp_microcode[i][1]);
			MMIO_OUT32(mmio, ATI_REG_MICROCODE_RAM_DATAL,
			    r300_cp_microcode[i][0]);
		}
	} else if (atic->is_radeon && atic->is_r200) {
		for (i = 0; i < 256; i++) {
			MMIO_OUT32(mmio, ATI_REG_MICROCODE_RAM_DATAH,
			    r200_cp_microcode[i][1]);
			MMIO_OUT32(mmio, ATI_REG_MICROCODE_RAM_DATAL,
			    r200_cp_microcode[i][0]);
		}
	} else if (atic->is_radeon && atic->is_r100) {
		for (i = 0; i < 256; i++) {
			MMIO_OUT32(mmio, ATI_REG_MICROCODE_RAM_DATAH,
			    radeon_cp_microcode[i][1]);
			MMIO_OUT32(mmio, ATI_REG_MICROCODE_RAM_DATAL,
			    radeon_cp_microcode[i][0]);
		}
	} else {
		for (i = 0; i < 256; i++) {
			MMIO_OUT32(mmio, ATI_REG_MICROCODE_RAM_DATAH,
			    r128_cce_microcode[i * 2]);
			MMIO_OUT32(mmio, ATI_REG_MICROCODE_RAM_DATAL,
			    r128_cce_microcode[i * 2 + 1]);
		}
	}
}

/* Required when reading from video memory after acceleration to make sure all
 * data has been flushed to video memory from the pixel cache.
 */
static void
ATIFlushPixelCache(ATIScreenInfo *atis)
{
	ATICardInfo *atic = atis->atic;
	char *mmio = atic->reg_base;
	CARD32 temp;
	TIMEOUT_LOCALS;

	if (atic->is_radeon) {
		temp = MMIO_IN32(mmio, RADEON_REG_RB3D_DSTCACHE_CTLSTAT);
		temp |= RADEON_RB3D_DC_FLUSH_ALL;
		MMIO_OUT32(mmio, RADEON_REG_RB3D_DSTCACHE_CTLSTAT, temp);

		WHILE_NOT_TIMEOUT(.2) {
			if ((MMIO_IN32(mmio, RADEON_REG_RB3D_DSTCACHE_CTLSTAT) &
			    RADEON_RB3D_DC_BUSY) == 0)
				break;
		}
	} else {
		temp = MMIO_IN32(mmio, R128_REG_PC_NGUI_CTLSTAT);
		temp |= R128_PC_FLUSH_ALL;
		MMIO_OUT32(mmio, R128_REG_PC_NGUI_CTLSTAT, temp);

		WHILE_NOT_TIMEOUT(.2) {
			if ((MMIO_IN32(mmio, R128_REG_PC_NGUI_CTLSTAT) &
			    R128_PC_BUSY) != R128_PC_BUSY)
				break;
		}
	}
	if (TIMEDOUT())
		ErrorF("Timeout flushing pixel cache.\n");
}

static void
ATIEngineReset(ATIScreenInfo *atis)
{
	ATICardInfo *atic = atis->atic;
	char *mmio = atic->reg_base;
	CARD32 clockcntlindex, mclkcntl;

#if DEBUG_FIFO
	ErrorF("Engine Reset!\n");
	ATIDebugFifo(atis);
#endif

	ATIFlushPixelCache(atis);

	clockcntlindex = MMIO_IN32(mmio, ATI_REG_CLOCK_CNTL_INDEX);
	if (atic->is_r300)
		R300CGWorkaround(atis);

	if (atic->is_radeon) {
		CARD32 host_path_cntl;

		mclkcntl = INPLL(mmio, RADEON_REG_MCLK_CNTL);

		OUTPLL(mmio, RADEON_REG_MCLK_CNTL, mclkcntl |
		    RADEON_FORCEON_MCLKA |
		    RADEON_FORCEON_MCLKB |
		    RADEON_FORCEON_YCLKA |
		    RADEON_FORCEON_YCLKB |
		    RADEON_FORCEON_MC |
		    RADEON_FORCEON_AIC);

		host_path_cntl = MMIO_IN32(mmio, RADEON_REG_HOST_PATH_CNTL);

		if (atic->is_r300) {
			MMIO_OUT32(mmio, RADEON_REG_RBBM_SOFT_RESET,
			    RADEON_SOFT_RESET_CP |
			    RADEON_SOFT_RESET_HI |
			    RADEON_SOFT_RESET_E2);
		} else {
			MMIO_OUT32(mmio, RADEON_REG_RBBM_SOFT_RESET,
			    RADEON_SOFT_RESET_CP |
			    RADEON_SOFT_RESET_SE |
			    RADEON_SOFT_RESET_RE |
			    RADEON_SOFT_RESET_PP |
			    RADEON_SOFT_RESET_E2 |
			    RADEON_SOFT_RESET_RB);
		}
		MMIO_IN32(mmio, RADEON_REG_RBBM_SOFT_RESET);
		MMIO_OUT32(mmio, RADEON_REG_RBBM_SOFT_RESET, 0);

		MMIO_OUT32(mmio, RADEON_REG_HOST_PATH_CNTL, host_path_cntl |
		    RADEON_HDP_SOFT_RESET);
		MMIO_IN32(mmio, RADEON_REG_HOST_PATH_CNTL);
		MMIO_OUT32(mmio, RADEON_REG_HOST_PATH_CNTL, host_path_cntl);

		MMIO_OUT32(mmio, ATI_REG_CLOCK_CNTL_INDEX, clockcntlindex);
		OUTPLL(mmio, RADEON_REG_MCLK_CNTL, mclkcntl);
		if (atic->is_r300)
			R300CGWorkaround(atis);
	} else {
		CARD32 temp;

		mclkcntl = INPLL(mmio, R128_REG_MCLK_CNTL);

		OUTPLL(mmio, R128_REG_MCLK_CNTL,
		    mclkcntl | R128_FORCE_GCP | R128_FORCE_PIPE3D_CP);

		temp = MMIO_IN32(mmio, R128_REG_GEN_RESET_CNTL);
		MMIO_OUT32(mmio, R128_REG_GEN_RESET_CNTL,
		    temp | R128_SOFT_RESET_GUI);
		temp = MMIO_IN32(mmio, R128_REG_GEN_RESET_CNTL);
		MMIO_OUT32(mmio, R128_REG_GEN_RESET_CNTL,
		    temp & ~R128_SOFT_RESET_GUI);
		temp = MMIO_IN32(mmio, R128_REG_GEN_RESET_CNTL);

		OUTPLL(mmio, R128_REG_MCLK_CNTL, mclkcntl);
		MMIO_OUT32(mmio, ATI_REG_CLOCK_CNTL_INDEX, clockcntlindex);
	}
#ifdef USE_DRI
	if (atis->using_dri) {
		ATIDRIDMAReset(atis);
		ATIDRIDMAStart(atis);
	}
#endif
}

static void
ATIWaitAvailMMIO(ATIScreenInfo *atis, int n)
{
	ATICardInfo *atic = atis->atic;
	char *mmio = atic->reg_base;
	TIMEOUT_LOCALS;

	if (atis->mmio_avail >= n) {
		atis->mmio_avail -= n;
		return;
	}
	if (atic->is_radeon) {
		WHILE_NOT_TIMEOUT(.2) {
			atis->mmio_avail = MMIO_IN32(mmio,
			    RADEON_REG_RBBM_STATUS) & RADEON_RBBM_FIFOCNT_MASK;
			if (atis->mmio_avail >= n)
				break;
		}
	} else {
		WHILE_NOT_TIMEOUT(.2) {
			atis->mmio_avail = MMIO_IN32(mmio, R128_REG_GUI_STAT) &
			    0xfff;
			if (atis->mmio_avail >= n)
				break;
		}
	}
	if (TIMEDOUT()) {
		ErrorF("Timeout waiting for %d MMIO slots.\n", n);
		ATIEngineReset(atis);
		ATIDrawSetup(atis->screen->pScreen);
	}
	atis->mmio_avail -= n;
}

static int
ATIGetAvailPrimary(ATIScreenInfo *atis)
{
	ATICardInfo *atic = atis->atic;
	char *mmio = atic->reg_base;

	if (atic->is_radeon) {
		int csq_stat, diff;
	
		csq_stat = MMIO_IN32(mmio, RADEON_REG_CP_CSQ_STAT);
		if (atic->is_r200)
			diff = ((csq_stat & R200_CSQ_WPTR_PRIMARY_MASK) >> 9) - 
			    (csq_stat & R200_CSQ_RPTR_PRIMARY_MASK);
		else
			diff = ((csq_stat & RADEON_CSQ_WPTR_PRIMARY_MASK) >> 8) - 
			    (csq_stat & RADEON_CSQ_RPTR_PRIMARY_MASK);
	
		if (diff < 0)
			return -diff;
		else
			return atis->cce_pri_size - diff;
	} else {
		return MMIO_IN32(mmio, R128_REG_PM4_STAT) &
		    R128_PM4_FIFOCNT_MASK;
	}
}

static void
ATIWaitAvailPrimary(ATIScreenInfo *atis, int n)
{
	TIMEOUT_LOCALS;

	if (atis->cce_pri_avail >= n) {
		atis->cce_pri_avail -= n;
		return;
	}

	WHILE_NOT_TIMEOUT(.2) {
		if (atis->cce_pri_avail >= n)
			break;
		atis->cce_pri_avail = ATIGetAvailPrimary(atis);
		if (atis->cce_pri_avail >= n)
			break;
	}
	if (TIMEDOUT()) {
		ErrorF("Timeout waiting for %d CCE slots (%d avail).\n", n,
		    atis->cce_pri_avail);
		ATIEngineReset(atis);
		ATIDrawSetup(atis->screen->pScreen);
	}
	atis->cce_pri_avail -= n;
}

void
ATIWaitIdle(ATIScreenInfo *atis)
{
	ATICardInfo *atic = atis->atic;
	char *mmio = atic->reg_base;
	TIMEOUT_LOCALS;

	if (atis->indirectBuffer != NULL)
		ATIFlushIndirect(atis, 0);

#ifdef USE_DRI
	if (atis->using_dri) {
		int ret = 0;
		int cmd = (atic->is_radeon ? DRM_RADEON_CP_IDLE :
		    DRM_R128_CCE_IDLE);
		WHILE_NOT_TIMEOUT(2) {
			ret = drmCommandNone(atic->drmFd, cmd);
			if (ret != -EBUSY)
				break;
		}
		if (TIMEDOUT()) {
			ATIDebugFifo(atis);
			FatalError("Timed out idling CCE (card hung)\n");
		}
		if (ret != 0)
			ErrorF("Failed to idle DMA, returned %d\n", ret);
		return;
	}
#endif

	if (!atic->is_radeon && (atis->using_pseudo || atis->using_dma)) {
		ATIWaitAvailPrimary(atis, atis->cce_pri_size);

		WHILE_NOT_TIMEOUT(.2) {
			if ((MMIO_IN32(mmio, R128_REG_PM4_STAT) &
			    (R128_PM4_BUSY | R128_PM4_GUI_ACTIVE)) == 0)
				break;
		}
		if (TIMEDOUT()) {
			ErrorF("Timeout idling CCE, resetting...\n");
			ATIEngineReset(atis);
			ATIDrawSetup(atis->screen->pScreen);
		}
	}

	/* Radeon CP idle is the same as MMIO idle. */
	if (atis->using_pio || atic->is_radeon) {
		/* Empty the fifo */
		ATIWaitAvailMMIO(atis, 64);

		if (atic->is_radeon) {
			WHILE_NOT_TIMEOUT(.2) {
				if ((MMIO_IN32(mmio, RADEON_REG_RBBM_STATUS) &
				    RADEON_RBBM_ACTIVE) == 0)
					break;
			}
		} else {
			WHILE_NOT_TIMEOUT(.2) {
				if ((MMIO_IN32(mmio, R128_REG_GUI_STAT) &
				    R128_GUI_ACTIVE) == 0)
					break;
			}
		}
		if (TIMEDOUT()) {
			ErrorF("Timeout idling accelerator, resetting...\n");
			ATIEngineReset(atis);
			ATIDrawSetup(atis->screen->pScreen);
		}
	}

	ATIFlushPixelCache(atis);

#if DEBUG_FIFO
	ErrorF("Idle?\n");
	ATIDebugFifo(atis);
#endif
}

dmaBuf *
ATIGetDMABuffer(ATIScreenInfo *atis)
{
	dmaBuf *buf;

	buf = (dmaBuf *)xalloc(sizeof(dmaBuf));
	if (buf == NULL)
		return NULL;

#ifdef USE_DRI
	if (atis->using_dri) {
		buf->drmBuf = ATIDRIGetBuffer(atis);
		if (buf->drmBuf == NULL) {
			xfree(buf);
			return NULL;
		}
		buf->size = buf->drmBuf->total;
		buf->used = buf->drmBuf->used;
		buf->address = buf->drmBuf->address;
		return buf;
	}
#endif /* USE_DRI */

	if (atis->using_dma)
		buf->size = atis->ring_len / 2;
	else
		buf->size = 512 * 1024;
	buf->address = xalloc(buf->size);
	if (buf->address == NULL) {
		xfree(buf);
		return NULL;
	}
	buf->used = 0;

	return buf;
}

/* Decode a type-3 packet into MMIO register writes. Only some type-3 packets
 * supported, and only partially.
 */
static void
ATIDispatchPacket3MMIO(ATIScreenInfo *atis, CARD32 header, CARD32 *addr,
    int count)
{
	ATICardInfo *atic = atis->atic;
	char *mmio = atic->reg_base;
	CARD32 settings;
	int i = 0;

	settings = addr[i++];

	if ((settings & ATI_GMC_SRC_PITCH_OFFSET_CNTL) != 0)
		MMIO_OUT32(mmio, ATI_REG_SRC_PITCH_OFFSET, addr[i++]);
	if ((settings & ATI_GMC_DST_PITCH_OFFSET_CNTL) != 0)
		MMIO_OUT32(mmio, ATI_REG_DST_PITCH_OFFSET, addr[i++]);
	if ((settings & ATI_GMC_BRUSH_MASK) == ATI_GMC_BRUSH_SOLID_COLOR)
		MMIO_OUT32(mmio, ATI_REG_DP_BRUSH_FRGD_CLR, addr[i++]);

	switch (header & (ATI_CCE_PACKETTYPE_MASK |
	    ATI_CCE_PACKET3_IT_OPCODE_MASK))
	{
	case ATI_CCE_PACKET3_PAINT_MULTI:
		while (i < count) {
			MMIO_OUT32(mmio, ATI_REG_DST_Y_X,
			    (addr[i] >> 16) | (addr[i] << 16));
			i++;
			MMIO_OUT32(mmio, ATI_REG_DST_HEIGHT_WIDTH,
			    (addr[i] >> 16) | (addr[i] << 16));
			i++;
		}
		break;
	case ATI_CCE_PACKET3_BITBLT_MULTI:
		while (i < count) {
			MMIO_OUT32(mmio, ATI_REG_SRC_Y_X,
			    (addr[i] >> 16) | (addr[i] << 16));
			i++;
			MMIO_OUT32(mmio, ATI_REG_DST_Y_X,
			    (addr[i] >> 16) | (addr[i] << 16));
			i++;
			MMIO_OUT32(mmio, ATI_REG_DST_HEIGHT_WIDTH,
			    (addr[i] >> 16) | (addr[i] << 16));
			i++;
		}
		break;
	default:
		ErrorF("Unsupported packet: 0x%x\n", header);
	}
}

/* Dispatch packets by decoding them and writing to registers.  Doesn't support
 * the type 3 packets.
 */
static void
ATIDispatchIndirectMMIO(ATIScreenInfo *atis)
{
	ATICardInfo *atic = atis->atic;
	dmaBuf *buf = atis->indirectBuffer;
	char *mmio = atic->reg_base;
	CARD32 *addr;
	CARD32 reg;
	int i, n, count;

	addr = (CARD32 *)((char *)buf->address + atis->indirectStart);
	count = (buf->used - atis->indirectStart) / 4;

	for (i = 0; i < count; i++) {
		CARD32 header = addr[i];

		switch (header & ATI_CCE_PACKETTYPE_MASK)
		{
		case ATI_CCE_PACKET0:
			n = ((header & ATI_CCE_PACKET0_COUNT_MASK) >> 16) + 1;
			reg = (header & ATI_CCE_PACKET0_REG_MASK) << 2;
			ATIWaitAvailMMIO(atis, n);
			while (n > 0) {
				i++;
				MMIO_OUT32(mmio, reg, addr[i]);
				if ((header & ATI_CCE_PACKET0_ONE_REG_WR) == 0)
					reg += 4;
				n--;
			}
			break;
		case ATI_CCE_PACKET1:
			reg = (header & ATI_CCE_PACKET1_REG_1) << 2;
			MMIO_OUT32(mmio, reg, addr[++i]);
			reg = ((header & ATI_CCE_PACKET1_REG_2) >>
			    ATI_CCE_PACKET1_REG_2_SHIFT) << 2;
			MMIO_OUT32(mmio, reg, addr[++i]);
			break;
		case ATI_CCE_PACKET2:
			/* PACKET2 is a no-op packet. */
			break;
		case ATI_CCE_PACKET3:
			n = ((header & ATI_CCE_PACKET3_COUNT_MASK) >> 16) + 1;
			ATIDispatchPacket3MMIO(atis, header, &addr[i], n);
			i += n;
			break;
		default:
			ErrorF("Unsupported packet: 0x%x\n", addr[i]);
		}
	}
}

/* Dispatch packets by sending them through the MMIO aperture. */
static void
R128DispatchIndirectPDMA(ATIScreenInfo *atis)
{
	ATICardInfo *atic = atis->atic;
	dmaBuf *buf = atis->indirectBuffer;
	char *mmio = atic->reg_base;
	CARD32 *addr;
	int count;

	addr = (CARD32 *)((char *)buf->address + atis->indirectStart);
	count = (buf->used - atis->indirectStart) / 4;

	while (count > 1) {
		ATIWaitAvailPrimary(atis, 2);
		MMIO_OUT32(mmio, R128_REG_PM4_FIFO_DATA_EVEN, *addr++);
		MMIO_OUT32(mmio, R128_REG_PM4_FIFO_DATA_ODD, *addr++);
		count -= 2;
	}

	/* Submit last DWORD if necessary. */
	if (count != 0) {
		ATIWaitAvailPrimary(atis, 2);
		MMIO_OUT32(mmio, R128_REG_PM4_FIFO_DATA_EVEN, *addr++);
		MMIO_OUT32(mmio, R128_REG_PM4_FIFO_DATA_ODD, ATI_CCE_PACKET2);
	}
}

/* Dispatch packets by sending them through the MMIO aperture, using the
 * primary CCE ring. */
static void
RadeonDispatchIndirectPDMA(ATIScreenInfo *atis)
{
	ATICardInfo *atic = atis->atic;
	dmaBuf *buf = atis->indirectBuffer;
	char *mmio = atic->reg_base;
	CARD32 *addr;
	int count, avail, reg, i;
	TIMEOUT_LOCALS;

	addr = (CARD32 *)((char *)buf->address + atis->indirectStart);
	count = (buf->used - atis->indirectStart) / 4;

	reg = RADEON_REG_CSQ_APER_PRIMARY;
	WHILE_NOT_TIMEOUT(3) {
		/* 3 seconds is empirical, using render_bench on an r100. */
		if (count <= 0)
			break;
		avail = ATIGetAvailPrimary(atis);
		for (i = 0; i < min(count, avail); i++) {
			MMIO_OUT32(mmio, reg, *addr++);
			if (reg == RADEON_REG_CSQ_APER_PRIMARY_END)
				reg = RADEON_REG_CSQ_APER_PRIMARY;
			else
				reg += 4;
		}
		count -= i;
	}
	if (TIMEDOUT()) {
		ErrorF("Timeout submitting packets, resetting...\n");
		ATIEngineReset(atis);
		ATIDrawSetup(atis->screen->pScreen);
	}
}


/* Dispatch packets by writing them to the (primary) ring buffer, which happens
 * to be in framebuffer memory.
 */
static void
R128DispatchIndirectDMA(ATIScreenInfo *atis)
{
	ATICardInfo *atic = atis->atic;
	dmaBuf *buf = atis->indirectBuffer;
	char *mmio = atic->reg_base;
	CARD32 *addr;
	int count, ring_count;
	TIMEOUT_LOCALS;

	addr = (CARD32 *)((char *)buf->address + atis->indirectStart);
	count = (buf->used - atis->indirectStart) / 4;
	ring_count = atis->ring_len / 4;

	WHILE_NOT_TIMEOUT(.2) {
		if (count <= 0)
			break;

		atis->ring_addr[atis->ring_write++] = *addr++;
		if (atis->ring_write >= ring_count)
			atis->ring_write = 0;
		while (atis->ring_write == atis->ring_read) {
			atis->ring_read = MMIO_IN32(mmio, ATI_REG_CCE_RPTR);
		}
		count--;
	}
	if (TIMEDOUT()) {
		ErrorF("Timeout submitting packets, resetting...\n");
		ATIEngineReset(atis);
		ATIDrawSetup(atis->screen->pScreen);
	}
		
	/* Workaround for some early Rage 128 ASIC spins where the CCE parser
	 * may read up to 32 DWORDS beyond the end of the ring buffer memory
	 * before wrapping around, if the ring buffer was empty and a <32 DWORD
	 * packet that wraps around the end of the ring buffer is submitted.
	 * To work around that, copy the beginning of the ring buffer past the
	 * end if that may happen.
	 */
	if (atis->ring_write < 32)
		memcpy(atis->ring_addr + ring_count, atis->ring_addr, 32 * 4);

	/* Update write pointer */
	MMIO_OUT32(mmio, ATI_REG_CCE_WPTR, atis->ring_write);
}

void
ATIFlushIndirect(ATIScreenInfo *atis, Bool discard)
{
	ATICardInfo *atic = atis->atic;
	dmaBuf *buf = atis->indirectBuffer;

	if ((atis->indirectStart == buf->used) && !discard)
		return;

#if DEBUG_FIFO
	ErrorF("Dispatching %d DWORDS\n", (buf->used - atis->indirectStart) /
	    4);
#endif

#ifdef USE_DRI
	if (atis->using_dri) {
		buf->drmBuf->used = buf->used;
		ATIDRIDispatchIndirect(atis, discard);
		if (discard) {
			buf->drmBuf = ATIDRIGetBuffer(atis);
			buf->size = buf->drmBuf->total;
			buf->used = buf->drmBuf->used;
			buf->address = buf->drmBuf->address;
			atis->indirectStart = 0;
		} else {
			/* Start on a double word boundary */
			atis->indirectStart = buf->used = (buf->used + 7) & ~7;
		}
		return;
	}
#endif /* USE_DRI */

	if (atis->using_dma && !atic->is_radeon)
		R128DispatchIndirectDMA(atis);
	else if (atis->using_pseudo) {
		if (atic->is_radeon)
			RadeonDispatchIndirectPDMA(atis);
		else
			R128DispatchIndirectPDMA(atis);
	} else
		ATIDispatchIndirectMMIO(atis);

	buf->used = 0;
	atis->indirectStart = 0;
}

static Bool
ATIInitAGP(ScreenPtr pScreen, int size)
{
	KdScreenPriv(pScreen);
	ATIScreenInfo(pScreenPriv);
	ATICardInfo(pScreenPriv);
	AgpInfoPtr agp_info;
	int screennum = atis->screen->mynum;

	if (atic->is_radeon)
		return FALSE;

	if (!KdAgpGARTSupported()) 
		return FALSE; 

	if (!KdAcquireGART(screennum))
		return FALSE;

	atis->agp_key = KdAllocateGARTMemory(screennum, size, 0, NULL);
	if (atis->agp_key == -1) {
		ErrorF("Failed to allocate %dKB GART memory\n", size/1024);
		KdReleaseGART(screennum);
		return FALSE;
	}

	if (!KdBindGARTMemory(screennum, atis->agp_key, 0)) {
		ErrorF("Failed to bind GART memory\n");
		KdReleaseGART(screennum);
		return FALSE;
	}

	agp_info = KdGetAGPInfo(screennum);
	if (agp_info == NULL) {
		KdUnbindGARTMemory(screennum, atis->agp_key);
		KdReleaseGART(screennum);
		return FALSE;
	}

	atis->agp_addr = KdMapDevice(agp_info->base, agp_info->size);
	if (atis->agp_addr == NULL) {
		ErrorF("Failed to map GART memory\n");
		KdUnbindGARTMemory(screennum, atis->agp_key);
		KdReleaseGART(screennum);
		free(agp_info);
		return FALSE;
	}
	KdSetMappedMode(agp_info->base, agp_info->size,
	    KD_MAPPED_MODE_FRAMEBUFFER);

	atis->agp_size = size;
	free(agp_info);

	return TRUE;
}

static void
ATIFiniAGP(ScreenPtr pScreen)
{
	KdScreenPriv(pScreen);
	ATIScreenInfo(pScreenPriv);
	int screennum = atis->screen->mynum;

	KdUnbindGARTMemory(screennum, atis->agp_key);
	KdReleaseGART(screennum);
	atis->agp_addr = NULL;
	atis->agp_size = 0;
}

static Bool
ATIPseudoDMAInit(ScreenPtr pScreen)
{
	KdScreenPriv(pScreen);
	ATIScreenInfo(pScreenPriv);
	ATICardInfo(pScreenPriv);
	char *mmio = atic->reg_base;

	if (atic->is_r300)
		return FALSE;

	ATIUploadMicrocode(atis);
	ATIEngineReset(atis);

	if (atic->is_r200) {
		MMIO_OUT32(mmio, RADEON_REG_CP_CSQ_CNTL,
		    RADEON_CSQ_PRIPIO_INDDIS);
		atis->cce_pri_size = MMIO_IN32(mmio, RADEON_REG_CP_CSQ_CNTL) &
		    R200_CSQ_CNT_PRIMARY_MASK;
		MMIO_OUT32(mmio, RADEON_REG_ME_CNTL, RADEON_ME_MODE_FREE_RUN);
	} if (atic->is_radeon) {
		MMIO_OUT32(mmio, RADEON_REG_CP_CSQ_CNTL,
		    RADEON_CSQ_PRIPIO_INDDIS);
		atis->cce_pri_size = MMIO_IN32(mmio, RADEON_REG_CP_CSQ_CNTL) &
		    RADEON_CSQ_CNT_PRIMARY_MASK;
		MMIO_OUT32(mmio, RADEON_REG_ME_CNTL, RADEON_ME_MODE_FREE_RUN);
	} else {
		MMIO_OUT32(mmio, R128_REG_PM4_BUFFER_CNTL, R128_PM4_192PIO |
		    R128_PM4_BUFFER_CNTL_NOUPDATE);
		atis->cce_pri_size = 192;
		MMIO_OUT32(mmio, R128_REG_PM4_MICRO_CNTL,
		    R128_PM4_MICRO_FREERUN);
	}

	return TRUE;
}

static Bool
ATIPseudoDMAFini(ScreenPtr pScreen)
{	KdScreenPriv(pScreen);
	ATIScreenInfo(pScreenPriv);
	ATICardInfo(pScreenPriv);
	char *mmio = atic->reg_base;

	if (atic->is_radeon) {
		MMIO_OUT32(mmio, RADEON_REG_ME_CNTL, 0);
		MMIO_OUT32(mmio, RADEON_REG_CP_CSQ_CNTL,
		    RADEON_CSQ_PRIDIS_INDDIS);
	} else {
		MMIO_OUT32(mmio, R128_REG_PM4_MICRO_CNTL, 0);
		MMIO_OUT32(mmio, R128_REG_PM4_BUFFER_CNTL,
		    R128_PM4_NONPM4 | R128_PM4_BUFFER_CNTL_NOUPDATE);
	}
	atis->cce_pri_size = 0;

	ATIEngineReset(atis);

	return TRUE;
}

static Bool
ATIDMAInit(ScreenPtr pScreen, Bool use_agp)
{
	KdScreenPriv(pScreen);
	ATIScreenInfo(pScreenPriv);
	ATICardInfo(pScreenPriv);
	char *mmio = atic->reg_base;
	int dma_offset;
	CARD32 tmp;

	/* XXX: Not for radeons.  Yet? */
	if (atic->is_radeon)
		return FALSE;

	if (use_agp) {
		if (1)
			return FALSE; /* XXX */
		/* Allocate a 1MB AGP space, but only use 128k + 128 for DMA.
		 * XXX: Should use the rest for things like scratch space.
		 */
		if (!ATIInitAGP(pScreen, 1024 * 1024))
			return FALSE;
		atis->ring_addr = atis->agp_addr;
		atis->ring_len = 128 * 1024;
		dma_offset = R128_AGP_OFFSET;
	} else {
		if (1)
			return FALSE; /* XXX */
		/* Allocate a 128K buffer, plus 32 DWORDS to give space for the
		 * R128 ASIC bug workaround.
		 */
		atis->dma_space = KdOffscreenAlloc(pScreen, 128 * 1024 + 128,
		    128, TRUE, NULL, NULL);
		if (atis->dma_space == NULL)
			return FALSE;
		atis->ring_addr = (CARD32 *)(atis->dma_space->offset +
		    pScreenPriv->screen->memory_base);
		atis->ring_len = 128 * 1024;
		dma_offset = atis->dma_space->offset;
	}

	ATIUploadMicrocode(atis);
	ATIEngineReset(atis);

	atis->ring_read = 0;
	atis->ring_write = 0;

	tmp = MMIO_IN32(mmio, ATI_REG_BUS_CNTL);
	MMIO_OUT32(mmio, ATI_REG_BUS_CNTL, tmp & ~ATI_BUS_MASTER_DIS);

	MMIO_OUT32(mmio, ATI_REG_CCE_RB_BASE, dma_offset);
	MMIO_OUT32(mmio, ATI_REG_CCE_WPTR, atis->ring_write);
	MMIO_OUT32(mmio, ATI_REG_CCE_RPTR, atis->ring_read);
	MMIO_OUT32(mmio, ATI_REG_CCE_RPTR_ADDR, 0 /* XXX? */);

	if (atic->is_r200) {
		MMIO_OUT32(mmio, RADEON_REG_CP_CSQ_CNTL,
		    RADEON_CSQ_PRIBM_INDBM);
		atis->cce_pri_size = MMIO_IN32(mmio, RADEON_REG_CP_CSQ_CNTL) &
		    R200_CSQ_CNT_PRIMARY_MASK;
		MMIO_OUT32(mmio, RADEON_REG_ME_CNTL, RADEON_ME_MODE_FREE_RUN);
	} else if (atic->is_radeon) {
		MMIO_OUT32(mmio, RADEON_REG_CP_CSQ_CNTL,
		    RADEON_CSQ_PRIBM_INDBM);
		atis->cce_pri_size = MMIO_IN32(mmio, RADEON_REG_CP_CSQ_CNTL) &
		    RADEON_CSQ_CNT_PRIMARY_MASK;
		MMIO_OUT32(mmio, RADEON_REG_ME_CNTL, RADEON_ME_MODE_FREE_RUN);
	} else {
		MMIO_OUT32(mmio, R128_REG_PM4_BUFFER_WM_CNTL,
		    ((R128_WATERMARK_L/4) << R128_WMA_SHIFT) |
		    ((R128_WATERMARK_M/4) << R128_WMB_SHIFT) |
		    ((R128_WATERMARK_N/4) << R128_WMC_SHIFT) |
		    ((R128_WATERMARK_K/64) << R128_WB_WM_SHIFT));
		/* The sample code reads from an undocumneted register
		 * (PM4_BUFFER_ADDR).  Perhaps it's a write posting thing?  Do
		 * a read in case that's it.
		 */
		MMIO_IN32(mmio, R128_REG_PM4_BUFFER_CNTL);
		if (use_agp) {
			/* XXX Magic num */
			MMIO_OUT32(mmio, R128_REG_PCI_GART_PAGE, 1);
			MMIO_OUT32(mmio, R128_REG_PM4_BUFFER_CNTL,
			    ATILog2(atis->ring_len) |
			    R128_PM4_192BM |
			    R128_PM4_BUFFER_CNTL_NOUPDATE);
		} else {
			MMIO_OUT32(mmio, R128_REG_PM4_BUFFER_CNTL,
			    ATILog2(atis->ring_len) |
			    R128_PM4_192BM |
			    R128_PM4_BUFFER_CNTL_NOUPDATE |
			    R128_PM4_IN_FRAME_BUFFER);
		}
		atis->cce_pri_size = 192;
		MMIO_IN32(mmio, R128_REG_PM4_BUFFER_CNTL);
		MMIO_OUT32(mmio, R128_REG_PM4_MICRO_CNTL,
		    R128_PM4_MICRO_FREERUN);
	}

	return TRUE;
}

static Bool
ATIDMAFini(ScreenPtr pScreen)
{
	KdScreenPriv(pScreen);
	ATIScreenInfo(pScreenPriv);
	ATICardInfo(pScreenPriv);
	char *mmio = atic->reg_base;

	if (atic->is_radeon) {
		MMIO_OUT32(mmio, RADEON_REG_ME_CNTL, 0);
		MMIO_OUT32(mmio, RADEON_REG_CP_CSQ_CNTL,
		    RADEON_CSQ_PRIDIS_INDDIS);
	} else {
		MMIO_OUT32(mmio, ATI_REG_CCE_WPTR,
		    atis->ring_write | R128_PM4_BUFFER_DL_DONE);
		MMIO_OUT32(mmio, R128_REG_PM4_MICRO_CNTL, 0);
		MMIO_OUT32(mmio, R128_REG_PM4_BUFFER_CNTL,
		    R128_PM4_NONPM4 | R128_PM4_BUFFER_CNTL_NOUPDATE);
	}
	atis->cce_pri_size = 0;

	ATIEngineReset(atis);

	if (atis->using_agp)
		ATIFiniAGP(pScreen);
	else
		KdOffscreenFree(pScreen, atis->dma_space);

	return TRUE;
}

void
ATIDMASetup(ScreenPtr pScreen)
{
	KdScreenPriv(pScreen);
	ATICardInfo(pScreenPriv);
	ATIScreenInfo(pScreenPriv);

#ifdef USE_DRI
	if (atis->using_dri)
		ATIDRIDMAStart(atis);
#endif /* USE_DRI */

	if (!atis->using_dri) {
		atis->using_agp = FALSE;
		if (atic->is_agp && ATIDMAInit(pScreen, TRUE)) {
			atis->using_agp = TRUE;
			atis->using_dma = TRUE;
		} else if (ATIDMAInit(pScreen, FALSE)) {
			atis->using_agp = FALSE;
			atis->using_dma = TRUE;
		} else if (ATIPseudoDMAInit(pScreen))
			atis->using_pseudo = TRUE;
		else
			atis->using_pio = TRUE;
	}

	atis->indirectBuffer = ATIGetDMABuffer(atis);
	if (atis->indirectBuffer == FALSE)
		FatalError("Failed to allocate DMA buffer.\n");

	if (atis->using_dri)
		ErrorF("Initialized %s DRI DMA\n",
		    atis->using_agp ? "AGP" : "PCI");
	else if (atis->using_dma && atis->using_agp)
		ErrorF("Initialized AGP DMA\n");
	else if (atis->using_dma)
		ErrorF("Initialized framebuffer pseudo-DMA\n");
	else if (atis->using_pseudo)
		ErrorF("Initialized pseudo-DMA\n");
	else if (atis->using_pio)
		ErrorF("Initialized PIO\n");
}

void
ATIDMATeardown(ScreenPtr pScreen)
{
	KdScreenPriv(pScreen);
	ATIScreenInfo(pScreenPriv);

	ATIWaitIdle(atis);

#ifdef USE_DRI
	if (atis->using_dri)
		ATIDRIDMAStop(atis);
#endif /* USE_DRI */

	if (atis->using_dma)
		ATIDMAFini(pScreen);

	if (atis->using_pseudo)
		ATIPseudoDMAFini(pScreen);

	if (atis->using_pio || atis->using_pseudo || atis->using_dma) {
		xfree(atis->indirectBuffer->address);
		xfree(atis->indirectBuffer);
	}
	atis->indirectBuffer = NULL;

	atis->using_pio = FALSE;
	atis->using_pseudo = FALSE;
	atis->using_dma = FALSE;
	atis->using_agp = FALSE;
}

