via_dma.c 19.8 KB
Newer Older
D
Dave Airlie 已提交
1
/* via_dma.c -- DMA support for the VIA Unichrome/Pro
D
Dave Airlie 已提交
2
 *
D
Dave Airlie 已提交
3 4 5 6 7
 * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
 * All Rights Reserved.
 *
 * Copyright 2004 Digeo, Inc., Palo Alto, CA, U.S.A.
 * All Rights Reserved.
D
Dave Airlie 已提交
8
 *
D
Dave Airlie 已提交
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 * Copyright 2004 The Unichrome project.
 * 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, sub license,
 * 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 (including the
 * next paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL
D
Dave Airlie 已提交
26 27 28
 * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS 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
D
Dave Airlie 已提交
29 30
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
D
Dave Airlie 已提交
31 32 33
 * Authors:
 *    Tungsten Graphics,
 *    Erdi Chen,
D
Dave Airlie 已提交
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
 *    Thomas Hellstrom.
 */

#include "drmP.h"
#include "drm.h"
#include "via_drm.h"
#include "via_drv.h"
#include "via_3d_reg.h"

#define CMDBUF_ALIGNMENT_SIZE   (0x100)
#define CMDBUF_ALIGNMENT_MASK   (0x0ff)

/* defines for VIA 3D registers */
#define VIA_REG_STATUS          0x400
#define VIA_REG_TRANSET         0x43C
#define VIA_REG_TRANSPACE       0x440

/* VIA_REG_STATUS(0x400): Engine Status */
#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_VR_QUEUE_BUSY       0x00020000	/* Virtual Queue is busy */

#define SetReg2DAGP(nReg, nData) {				\
	*((uint32_t *)(vb)) = ((nReg) >> 2) | HALCYON_HEADER1;	\
	*((uint32_t *)(vb) + 1) = (nData);			\
	vb = ((uint32_t *)vb) + 2;				\
	dev_priv->dma_low +=8;					\
}

D
Dave Airlie 已提交
64
#define via_flush_write_combine() DRM_MEMORYBARRIER()
D
Dave Airlie 已提交
65 66 67 68

#define VIA_OUT_RING_QW(w1,w2)			\
	*vb++ = (w1);				\
	*vb++ = (w2);				\
D
Dave Airlie 已提交
69
	dev_priv->dma_low += 8;
D
Dave Airlie 已提交
70 71 72 73 74 75

static void via_cmdbuf_start(drm_via_private_t * dev_priv);
static void via_cmdbuf_pause(drm_via_private_t * dev_priv);
static void via_cmdbuf_reset(drm_via_private_t * dev_priv);
static void via_cmdbuf_rewind(drm_via_private_t * dev_priv);
static int via_wait_idle(drm_via_private_t * dev_priv);
D
Dave Airlie 已提交
76
static void via_pad_cache(drm_via_private_t * dev_priv, int qwords);
D
Dave Airlie 已提交
77 78 79 80 81

/*
 * Free space in command buffer.
 */

D
Dave Airlie 已提交
82
static uint32_t via_cmdbuf_space(drm_via_private_t * dev_priv)
D
Dave Airlie 已提交
83
{
D
Dave Airlie 已提交
84
	uint32_t agp_base = dev_priv->dma_offset + (uint32_t) dev_priv->agpAddr;
D
Dave Airlie 已提交
85
	uint32_t hw_addr = *(dev_priv->hw_addr_ptr) - agp_base;
D
Dave Airlie 已提交
86 87 88

	return ((hw_addr <= dev_priv->dma_low) ?
		(dev_priv->dma_high + hw_addr - dev_priv->dma_low) :
D
Dave Airlie 已提交
89 90 91 92 93 94 95
		(hw_addr - dev_priv->dma_low));
}

/*
 * How much does the command regulator lag behind?
 */

D
Dave Airlie 已提交
96
static uint32_t via_cmdbuf_lag(drm_via_private_t * dev_priv)
D
Dave Airlie 已提交
97
{
D
Dave Airlie 已提交
98
	uint32_t agp_base = dev_priv->dma_offset + (uint32_t) dev_priv->agpAddr;
D
Dave Airlie 已提交
99
	uint32_t hw_addr = *(dev_priv->hw_addr_ptr) - agp_base;
D
Dave Airlie 已提交
100 101 102

	return ((hw_addr <= dev_priv->dma_low) ?
		(dev_priv->dma_low - hw_addr) :
D
Dave Airlie 已提交
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
		(dev_priv->dma_wrap + dev_priv->dma_low - hw_addr));
}

/*
 * Check that the given size fits in the buffer, otherwise wait.
 */

static inline int
via_cmdbuf_wait(drm_via_private_t * dev_priv, unsigned int size)
{
	uint32_t agp_base = dev_priv->dma_offset + (uint32_t) dev_priv->agpAddr;
	uint32_t cur_addr, hw_addr, next_addr;
	volatile uint32_t *hw_addr_ptr;
	uint32_t count;
	hw_addr_ptr = dev_priv->hw_addr_ptr;
	cur_addr = dev_priv->dma_low;
D
Dave Airlie 已提交
119
	next_addr = cur_addr + size + 512 * 1024;
D
Dave Airlie 已提交
120 121
	count = 1000000;
	do {
D
Dave Airlie 已提交
122
		hw_addr = *hw_addr_ptr - agp_base;
D
Dave Airlie 已提交
123
		if (count-- == 0) {
D
Dave Airlie 已提交
124 125 126
			DRM_ERROR
			    ("via_cmdbuf_wait timed out hw %x cur_addr %x next_addr %x\n",
			     hw_addr, cur_addr, next_addr);
D
Dave Airlie 已提交
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
			return -1;
		}
	} while ((cur_addr < hw_addr) && (next_addr >= hw_addr));
	return 0;
}

/*
 * Checks whether buffer head has reach the end. Rewind the ring buffer
 * when necessary.
 *
 * Returns virtual pointer to ring buffer.
 */

static inline uint32_t *via_check_dma(drm_via_private_t * dev_priv,
				      unsigned int size)
{
D
Dave Airlie 已提交
143 144
	if ((dev_priv->dma_low + size + 4 * CMDBUF_ALIGNMENT_SIZE) >
	    dev_priv->dma_high) {
D
Dave Airlie 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157
		via_cmdbuf_rewind(dev_priv);
	}
	if (via_cmdbuf_wait(dev_priv, size) != 0) {
		return NULL;
	}

	return (uint32_t *) (dev_priv->dma_ptr + dev_priv->dma_low);
}

int via_dma_cleanup(drm_device_t * dev)
{
	if (dev->dev_private) {
		drm_via_private_t *dev_priv =
D
Dave Airlie 已提交
158
		    (drm_via_private_t *) dev->dev_private;
D
Dave Airlie 已提交
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187

		if (dev_priv->ring.virtual_start) {
			via_cmdbuf_reset(dev_priv);

			drm_core_ioremapfree(&dev_priv->ring.map, dev);
			dev_priv->ring.virtual_start = NULL;
		}

	}

	return 0;
}

static int via_initialize(drm_device_t * dev,
			  drm_via_private_t * dev_priv,
			  drm_via_dma_init_t * init)
{
	if (!dev_priv || !dev_priv->mmio) {
		DRM_ERROR("via_dma_init called before via_map_init\n");
		return DRM_ERR(EFAULT);
	}

	if (dev_priv->ring.virtual_start != NULL) {
		DRM_ERROR("%s called again without calling cleanup\n",
			  __FUNCTION__);
		return DRM_ERR(EFAULT);
	}

	if (!dev->agp || !dev->agp->base) {
D
Dave Airlie 已提交
188
		DRM_ERROR("%s called with no agp memory available\n",
D
Dave Airlie 已提交
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
			  __FUNCTION__);
		return DRM_ERR(EFAULT);
	}

	dev_priv->ring.map.offset = dev->agp->base + init->offset;
	dev_priv->ring.map.size = init->size;
	dev_priv->ring.map.type = 0;
	dev_priv->ring.map.flags = 0;
	dev_priv->ring.map.mtrr = 0;

	drm_core_ioremap(&dev_priv->ring.map, dev);

	if (dev_priv->ring.map.handle == NULL) {
		via_dma_cleanup(dev);
		DRM_ERROR("can not ioremap virtual address for"
			  " ring buffer\n");
		return DRM_ERR(ENOMEM);
	}

	dev_priv->ring.virtual_start = dev_priv->ring.map.handle;

	dev_priv->dma_ptr = dev_priv->ring.virtual_start;
	dev_priv->dma_low = 0;
	dev_priv->dma_high = init->size;
	dev_priv->dma_wrap = init->size;
	dev_priv->dma_offset = init->offset;
	dev_priv->last_pause_ptr = NULL;
D
Dave Airlie 已提交
216 217 218
	dev_priv->hw_addr_ptr =
		(volatile uint32_t *)((char *)dev_priv->mmio->handle +
		init->reg_pause_addr);
D
Dave Airlie 已提交
219 220 221 222 223 224 225 226 227 228 229 230 231

	via_cmdbuf_start(dev_priv);

	return 0;
}

int via_dma_init(DRM_IOCTL_ARGS)
{
	DRM_DEVICE;
	drm_via_private_t *dev_priv = (drm_via_private_t *) dev->dev_private;
	drm_via_dma_init_t init;
	int retcode = 0;

232
	DRM_COPY_FROM_USER_IOCTL(init, (drm_via_dma_init_t __user *) data,
D
Dave Airlie 已提交
233 234 235 236
				 sizeof(init));

	switch (init.func) {
	case VIA_INIT_DMA:
D
Dave Airlie 已提交
237
		if (!DRM_SUSER(DRM_CURPROC))
D
Dave Airlie 已提交
238 239 240 241 242
			retcode = DRM_ERR(EPERM);
		else
			retcode = via_initialize(dev, dev_priv, &init);
		break;
	case VIA_CLEANUP_DMA:
D
Dave Airlie 已提交
243
		if (!DRM_SUSER(DRM_CURPROC))
D
Dave Airlie 已提交
244 245 246 247
			retcode = DRM_ERR(EPERM);
		else
			retcode = via_dma_cleanup(dev);
		break;
D
Dave Airlie 已提交
248 249 250 251
	case VIA_DMA_INITIALIZED:
		retcode = (dev_priv->ring.virtual_start != NULL) ?
		    0 : DRM_ERR(EFAULT);
		break;
D
Dave Airlie 已提交
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
	default:
		retcode = DRM_ERR(EINVAL);
		break;
	}

	return retcode;
}

static int via_dispatch_cmdbuffer(drm_device_t * dev, drm_via_cmdbuffer_t * cmd)
{
	drm_via_private_t *dev_priv;
	uint32_t *vb;
	int ret;

	dev_priv = (drm_via_private_t *) dev->dev_private;

	if (dev_priv->ring.virtual_start == NULL) {
		DRM_ERROR("%s called without initializing AGP ring buffer.\n",
			  __FUNCTION__);
		return DRM_ERR(EFAULT);
	}

	if (cmd->size > VIA_PCI_BUF_SIZE) {
		return DRM_ERR(ENOMEM);
D
Dave Airlie 已提交
276
	}
D
Dave Airlie 已提交
277 278 279 280 281 282 283 284 285 286

	if (DRM_COPY_FROM_USER(dev_priv->pci_buf, cmd->buf, cmd->size))
		return DRM_ERR(EFAULT);

	/*
	 * Running this function on AGP memory is dead slow. Therefore
	 * we run it on a temporary cacheable system memory buffer and
	 * copy it to AGP memory when ready.
	 */

D
Dave Airlie 已提交
287 288 289
	if ((ret =
	     via_verify_command_stream((uint32_t *) dev_priv->pci_buf,
				       cmd->size, dev, 1))) {
D
Dave Airlie 已提交
290 291
		return ret;
	}
D
Dave Airlie 已提交
292

D
Dave Airlie 已提交
293 294 295 296 297 298
	vb = via_check_dma(dev_priv, (cmd->size < 0x100) ? 0x102 : cmd->size);
	if (vb == NULL) {
		return DRM_ERR(EAGAIN);
	}

	memcpy(vb, dev_priv->pci_buf, cmd->size);
D
Dave Airlie 已提交
299

D
Dave Airlie 已提交
300 301 302 303 304 305 306 307
	dev_priv->dma_low += cmd->size;

	/*
	 * Small submissions somehow stalls the CPU. (AGP cache effects?)
	 * pad to greater size.
	 */

	if (cmd->size < 0x100)
D
Dave Airlie 已提交
308
		via_pad_cache(dev_priv, (0x100 - cmd->size) >> 3);
D
Dave Airlie 已提交
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
	via_cmdbuf_pause(dev_priv);

	return 0;
}

int via_driver_dma_quiescent(drm_device_t * dev)
{
	drm_via_private_t *dev_priv = dev->dev_private;

	if (!via_wait_idle(dev_priv)) {
		return DRM_ERR(EBUSY);
	}
	return 0;
}

int via_flush_ioctl(DRM_IOCTL_ARGS)
{
	DRM_DEVICE;

D
Dave Airlie 已提交
328
	LOCK_TEST_WITH_RETURN(dev, filp);
D
Dave Airlie 已提交
329 330 331 332 333 334 335 336 337 338

	return via_driver_dma_quiescent(dev);
}

int via_cmdbuffer(DRM_IOCTL_ARGS)
{
	DRM_DEVICE;
	drm_via_cmdbuffer_t cmdbuf;
	int ret;

D
Dave Airlie 已提交
339
	LOCK_TEST_WITH_RETURN(dev, filp);
D
Dave Airlie 已提交
340

341
	DRM_COPY_FROM_USER_IOCTL(cmdbuf, (drm_via_cmdbuffer_t __user *) data,
D
Dave Airlie 已提交
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
				 sizeof(cmdbuf));

	DRM_DEBUG("via cmdbuffer, buf %p size %lu\n", cmdbuf.buf, cmdbuf.size);

	ret = via_dispatch_cmdbuffer(dev, &cmdbuf);
	if (ret) {
		return ret;
	}

	return 0;
}

static int via_dispatch_pci_cmdbuffer(drm_device_t * dev,
				      drm_via_cmdbuffer_t * cmd)
{
	drm_via_private_t *dev_priv = dev->dev_private;
	int ret;

	if (cmd->size > VIA_PCI_BUF_SIZE) {
		return DRM_ERR(ENOMEM);
D
Dave Airlie 已提交
362
	}
D
Dave Airlie 已提交
363 364
	if (DRM_COPY_FROM_USER(dev_priv->pci_buf, cmd->buf, cmd->size))
		return DRM_ERR(EFAULT);
D
Dave Airlie 已提交
365 366 367 368

	if ((ret =
	     via_verify_command_stream((uint32_t *) dev_priv->pci_buf,
				       cmd->size, dev, 0))) {
D
Dave Airlie 已提交
369 370
		return ret;
	}
D
Dave Airlie 已提交
371 372 373 374

	ret =
	    via_parse_command_stream(dev, (const uint32_t *)dev_priv->pci_buf,
				     cmd->size);
D
Dave Airlie 已提交
375 376 377 378 379 380 381 382 383
	return ret;
}

int via_pci_cmdbuffer(DRM_IOCTL_ARGS)
{
	DRM_DEVICE;
	drm_via_cmdbuffer_t cmdbuf;
	int ret;

D
Dave Airlie 已提交
384
	LOCK_TEST_WITH_RETURN(dev, filp);
D
Dave Airlie 已提交
385

386
	DRM_COPY_FROM_USER_IOCTL(cmdbuf, (drm_via_cmdbuffer_t __user *) data,
D
Dave Airlie 已提交
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
				 sizeof(cmdbuf));

	DRM_DEBUG("via_pci_cmdbuffer, buf %p size %lu\n", cmdbuf.buf,
		  cmdbuf.size);

	ret = via_dispatch_pci_cmdbuffer(dev, &cmdbuf);
	if (ret) {
		return ret;
	}

	return 0;
}

static inline uint32_t *via_align_buffer(drm_via_private_t * dev_priv,
					 uint32_t * vb, int qw_count)
{
D
Dave Airlie 已提交
403
	for (; qw_count > 0; --qw_count) {
D
Dave Airlie 已提交
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
		VIA_OUT_RING_QW(HC_DUMMY, HC_DUMMY);
	}
	return vb;
}

/*
 * This function is used internally by ring buffer mangement code.
 *
 * Returns virtual pointer to ring buffer.
 */
static inline uint32_t *via_get_dma(drm_via_private_t * dev_priv)
{
	return (uint32_t *) (dev_priv->dma_ptr + dev_priv->dma_low);
}

/*
 * Hooks a segment of data into the tail of the ring-buffer by
 * modifying the pause address stored in the buffer itself. If
 * the regulator has already paused, restart it.
 */
D
Dave Airlie 已提交
424
static int via_hook_segment(drm_via_private_t * dev_priv,
D
Dave Airlie 已提交
425 426 427 428 429 430 431
			    uint32_t pause_addr_hi, uint32_t pause_addr_lo,
			    int no_pci_fire)
{
	int paused, count;
	volatile uint32_t *paused_at = dev_priv->last_pause_ptr;

	via_flush_write_combine();
D
Dave Airlie 已提交
432
	while (!*(via_get_dma(dev_priv) - 1)) ;
D
Dave Airlie 已提交
433 434 435 436 437 438 439 440
	*dev_priv->last_pause_ptr = pause_addr_lo;
	via_flush_write_combine();

	/*
	 * The below statement is inserted to really force the flush.
	 * Not sure it is needed.
	 */

D
Dave Airlie 已提交
441
	while (!*dev_priv->last_pause_ptr) ;
D
Dave Airlie 已提交
442
	dev_priv->last_pause_ptr = via_get_dma(dev_priv) - 1;
D
Dave Airlie 已提交
443
	while (!*dev_priv->last_pause_ptr) ;
D
Dave Airlie 已提交
444 445

	paused = 0;
D
Dave Airlie 已提交
446
	count = 20;
D
Dave Airlie 已提交
447

D
Dave Airlie 已提交
448
	while (!(paused = (VIA_READ(0x41c) & 0x80000000)) && count--) ;
D
Dave Airlie 已提交
449 450 451
	if ((count <= 8) && (count >= 0)) {
		uint32_t rgtr, ptr;
		rgtr = *(dev_priv->hw_addr_ptr);
D
Dave Airlie 已提交
452 453 454
		ptr = ((volatile char *)dev_priv->last_pause_ptr -
		      dev_priv->dma_ptr) + dev_priv->dma_offset +
		      (uint32_t) dev_priv->agpAddr + 4 - CMDBUF_ALIGNMENT_SIZE;
D
Dave Airlie 已提交
455
		if (rgtr <= ptr) {
D
Dave Airlie 已提交
456 457 458 459 460 461
			DRM_ERROR
			    ("Command regulator\npaused at count %d, address %x, "
			     "while current pause address is %x.\n"
			     "Please mail this message to "
			     "<unichrome-devel@lists.sourceforge.net>\n", count,
			     rgtr, ptr);
D
Dave Airlie 已提交
462 463
		}
	}
D
Dave Airlie 已提交
464

D
Dave Airlie 已提交
465
	if (paused && !no_pci_fire) {
D
Dave Airlie 已提交
466
		uint32_t rgtr, ptr;
D
Dave Airlie 已提交
467 468 469
		uint32_t ptr_low;

		count = 1000000;
D
Dave Airlie 已提交
470 471 472
		while ((VIA_READ(VIA_REG_STATUS) & VIA_CMD_RGTR_BUSY)
		       && count--) ;

D
Dave Airlie 已提交
473
		rgtr = *(dev_priv->hw_addr_ptr);
D
Dave Airlie 已提交
474
		ptr = ((volatile char *)paused_at - dev_priv->dma_ptr) +
D
Dave Airlie 已提交
475
		    dev_priv->dma_offset + (uint32_t) dev_priv->agpAddr + 4;
D
Dave Airlie 已提交
476

D
Dave Airlie 已提交
477 478
		ptr_low = (ptr > 3 * CMDBUF_ALIGNMENT_SIZE) ?
		    ptr - 3 * CMDBUF_ALIGNMENT_SIZE : 0;
D
Dave Airlie 已提交
479 480 481 482
		if (rgtr <= ptr && rgtr >= ptr_low) {
			VIA_WRITE(VIA_REG_TRANSET, (HC_ParaType_PreCR << 16));
			VIA_WRITE(VIA_REG_TRANSPACE, pause_addr_hi);
			VIA_WRITE(VIA_REG_TRANSPACE, pause_addr_lo);
D
Dave Airlie 已提交
483
		}
D
Dave Airlie 已提交
484 485 486 487 488 489 490 491 492 493 494 495 496 497
	}
	return paused;
}

static int via_wait_idle(drm_via_private_t * dev_priv)
{
	int count = 10000000;
	while (count-- && (VIA_READ(VIA_REG_STATUS) &
			   (VIA_CMD_RGTR_BUSY | VIA_2D_ENG_BUSY |
			    VIA_3D_ENG_BUSY))) ;
	return count;
}

static uint32_t *via_align_cmd(drm_via_private_t * dev_priv, uint32_t cmd_type,
D
Dave Airlie 已提交
498 499
			       uint32_t addr, uint32_t * cmd_addr_hi,
			       uint32_t * cmd_addr_lo, int skip_wait)
D
Dave Airlie 已提交
500 501 502 503 504 505 506
{
	uint32_t agp_base;
	uint32_t cmd_addr, addr_lo, addr_hi;
	uint32_t *vb;
	uint32_t qw_pad_count;

	if (!skip_wait)
D
Dave Airlie 已提交
507
		via_cmdbuf_wait(dev_priv, 2 * CMDBUF_ALIGNMENT_SIZE);
D
Dave Airlie 已提交
508 509

	vb = via_get_dma(dev_priv);
D
Dave Airlie 已提交
510 511
	VIA_OUT_RING_QW(HC_HEADER2 | ((VIA_REG_TRANSET >> 2) << 12) |
			(VIA_REG_TRANSPACE >> 2), HC_ParaType_PreCR << 16);
D
Dave Airlie 已提交
512 513
	agp_base = dev_priv->dma_offset + (uint32_t) dev_priv->agpAddr;
	qw_pad_count = (CMDBUF_ALIGNMENT_SIZE >> 3) -
D
Dave Airlie 已提交
514
	    ((dev_priv->dma_low & CMDBUF_ALIGNMENT_MASK) >> 3);
D
Dave Airlie 已提交
515

D
Dave Airlie 已提交
516 517
	cmd_addr = (addr) ? addr :
	    agp_base + dev_priv->dma_low - 8 + (qw_pad_count << 3);
D
Dave Airlie 已提交
518 519 520 521 522
	addr_lo = ((HC_SubA_HAGPBpL << 24) | (cmd_type & HC_HAGPBpID_MASK) |
		   (cmd_addr & HC_HAGPBpL_MASK));
	addr_hi = ((HC_SubA_HAGPBpH << 24) | (cmd_addr >> 24));

	vb = via_align_buffer(dev_priv, vb, qw_pad_count - 1);
D
Dave Airlie 已提交
523
	VIA_OUT_RING_QW(*cmd_addr_hi = addr_hi, *cmd_addr_lo = addr_lo);
D
Dave Airlie 已提交
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
	return vb;
}

static void via_cmdbuf_start(drm_via_private_t * dev_priv)
{
	uint32_t pause_addr_lo, pause_addr_hi;
	uint32_t start_addr, start_addr_lo;
	uint32_t end_addr, end_addr_lo;
	uint32_t command;
	uint32_t agp_base;

	dev_priv->dma_low = 0;

	agp_base = dev_priv->dma_offset + (uint32_t) dev_priv->agpAddr;
	start_addr = agp_base;
	end_addr = agp_base + dev_priv->dma_high;

	start_addr_lo = ((HC_SubA_HAGPBstL << 24) | (start_addr & 0xFFFFFF));
	end_addr_lo = ((HC_SubA_HAGPBendL << 24) | (end_addr & 0xFFFFFF));
	command = ((HC_SubA_HAGPCMNT << 24) | (start_addr >> 24) |
		   ((end_addr & 0xff000000) >> 16));

D
Dave Airlie 已提交
546 547 548
	dev_priv->last_pause_ptr =
	    via_align_cmd(dev_priv, HC_HAGPBpID_PAUSE, 0,
			  &pause_addr_hi, &pause_addr_lo, 1) - 1;
D
Dave Airlie 已提交
549 550

	via_flush_write_combine();
D
Dave Airlie 已提交
551
	while (!*dev_priv->last_pause_ptr) ;
D
Dave Airlie 已提交
552 553 554 555 556 557 558 559 560 561 562 563

	VIA_WRITE(VIA_REG_TRANSET, (HC_ParaType_PreCR << 16));
	VIA_WRITE(VIA_REG_TRANSPACE, command);
	VIA_WRITE(VIA_REG_TRANSPACE, start_addr_lo);
	VIA_WRITE(VIA_REG_TRANSPACE, end_addr_lo);

	VIA_WRITE(VIA_REG_TRANSPACE, pause_addr_hi);
	VIA_WRITE(VIA_REG_TRANSPACE, pause_addr_lo);

	VIA_WRITE(VIA_REG_TRANSPACE, command | HC_HAGPCMNT_MASK);
}

D
Dave Airlie 已提交
564
static void via_pad_cache(drm_via_private_t * dev_priv, int qwords)
D
Dave Airlie 已提交
565 566 567 568 569
{
	uint32_t *vb;

	via_cmdbuf_wait(dev_priv, qwords + 2);
	vb = via_get_dma(dev_priv);
D
Dave Airlie 已提交
570 571
	VIA_OUT_RING_QW(HC_HEADER2, HC_ParaType_NotTex << 16);
	via_align_buffer(dev_priv, vb, qwords);
D
Dave Airlie 已提交
572 573 574 575 576 577 578
}

static inline void via_dummy_bitblt(drm_via_private_t * dev_priv)
{
	uint32_t *vb = via_get_dma(dev_priv);
	SetReg2DAGP(0x0C, (0 | (0 << 16)));
	SetReg2DAGP(0x10, 0 | (0 << 16));
D
Dave Airlie 已提交
579
	SetReg2DAGP(0x0, 0x1 | 0x2000 | 0xAA000000);
D
Dave Airlie 已提交
580 581 582 583 584 585 586 587 588 589 590
}

static void via_cmdbuf_jump(drm_via_private_t * dev_priv)
{
	uint32_t agp_base;
	uint32_t pause_addr_lo, pause_addr_hi;
	uint32_t jump_addr_lo, jump_addr_hi;
	volatile uint32_t *last_pause_ptr;
	uint32_t dma_low_save1, dma_low_save2;

	agp_base = dev_priv->dma_offset + (uint32_t) dev_priv->agpAddr;
D
Dave Airlie 已提交
591
	via_align_cmd(dev_priv, HC_HAGPBpID_JUMP, 0, &jump_addr_hi,
D
Dave Airlie 已提交
592 593
		      &jump_addr_lo, 0);

D
Dave Airlie 已提交
594
	dev_priv->dma_wrap = dev_priv->dma_low;
D
Dave Airlie 已提交
595 596 597 598 599 600 601 602 603 604 605

	/*
	 * Wrap command buffer to the beginning.
	 */

	dev_priv->dma_low = 0;
	if (via_cmdbuf_wait(dev_priv, CMDBUF_ALIGNMENT_SIZE) != 0) {
		DRM_ERROR("via_cmdbuf_jump failed\n");
	}

	via_dummy_bitblt(dev_priv);
D
Dave Airlie 已提交
606
	via_dummy_bitblt(dev_priv);
D
Dave Airlie 已提交
607

D
Dave Airlie 已提交
608 609 610 611
	last_pause_ptr =
	    via_align_cmd(dev_priv, HC_HAGPBpID_PAUSE, 0, &pause_addr_hi,
			  &pause_addr_lo, 0) - 1;
	via_align_cmd(dev_priv, HC_HAGPBpID_PAUSE, 0, &pause_addr_hi,
D
Dave Airlie 已提交
612 613 614 615 616 617 618 619 620 621 622 623 624 625
		      &pause_addr_lo, 0);

	*last_pause_ptr = pause_addr_lo;
	dma_low_save1 = dev_priv->dma_low;

	/*
	 * Now, set a trap that will pause the regulator if it tries to rerun the old
	 * command buffer. (Which may happen if via_hook_segment detecs a command regulator pause
	 * and reissues the jump command over PCI, while the regulator has already taken the jump
	 * and actually paused at the current buffer end).
	 * There appears to be no other way to detect this condition, since the hw_addr_pointer
	 * does not seem to get updated immediately when a jump occurs.
	 */

D
Dave Airlie 已提交
626 627 628 629
	last_pause_ptr =
	    via_align_cmd(dev_priv, HC_HAGPBpID_PAUSE, 0, &pause_addr_hi,
			  &pause_addr_lo, 0) - 1;
	via_align_cmd(dev_priv, HC_HAGPBpID_PAUSE, 0, &pause_addr_hi,
D
Dave Airlie 已提交
630 631 632 633
		      &pause_addr_lo, 0);
	*last_pause_ptr = pause_addr_lo;

	dma_low_save2 = dev_priv->dma_low;
D
Dave Airlie 已提交
634 635
	dev_priv->dma_low = dma_low_save1;
	via_hook_segment(dev_priv, jump_addr_hi, jump_addr_lo, 0);
D
Dave Airlie 已提交
636
	dev_priv->dma_low = dma_low_save2;
D
Dave Airlie 已提交
637
	via_hook_segment(dev_priv, pause_addr_hi, pause_addr_lo, 0);
D
Dave Airlie 已提交
638 639 640 641
}

static void via_cmdbuf_rewind(drm_via_private_t * dev_priv)
{
D
Dave Airlie 已提交
642
	via_cmdbuf_jump(dev_priv);
D
Dave Airlie 已提交
643 644 645 646 647 648 649
}

static void via_cmdbuf_flush(drm_via_private_t * dev_priv, uint32_t cmd_type)
{
	uint32_t pause_addr_lo, pause_addr_hi;

	via_align_cmd(dev_priv, cmd_type, 0, &pause_addr_hi, &pause_addr_lo, 0);
D
Dave Airlie 已提交
650
	via_hook_segment(dev_priv, pause_addr_hi, pause_addr_lo, 0);
D
Dave Airlie 已提交
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
}

static void via_cmdbuf_pause(drm_via_private_t * dev_priv)
{
	via_cmdbuf_flush(dev_priv, HC_HAGPBpID_PAUSE);
}

static void via_cmdbuf_reset(drm_via_private_t * dev_priv)
{
	via_cmdbuf_flush(dev_priv, HC_HAGPBpID_STOP);
	via_wait_idle(dev_priv);
}

/*
 * User interface to the space and lag functions.
 */

D
Dave Airlie 已提交
668
int via_cmdbuf_size(DRM_IOCTL_ARGS)
D
Dave Airlie 已提交
669 670 671 672 673 674 675 676
{
	DRM_DEVICE;
	drm_via_cmdbuf_size_t d_siz;
	int ret = 0;
	uint32_t tmp_size, count;
	drm_via_private_t *dev_priv;

	DRM_DEBUG("via cmdbuf_size\n");
D
Dave Airlie 已提交
677
	LOCK_TEST_WITH_RETURN(dev, filp);
D
Dave Airlie 已提交
678 679 680 681 682 683 684 685 686

	dev_priv = (drm_via_private_t *) dev->dev_private;

	if (dev_priv->ring.virtual_start == NULL) {
		DRM_ERROR("%s called without initializing AGP ring buffer.\n",
			  __FUNCTION__);
		return DRM_ERR(EFAULT);
	}

687
	DRM_COPY_FROM_USER_IOCTL(d_siz, (drm_via_cmdbuf_size_t __user *) data,
D
Dave Airlie 已提交
688 689 690 691
				 sizeof(d_siz));

	count = 1000000;
	tmp_size = d_siz.size;
D
Dave Airlie 已提交
692
	switch (d_siz.func) {
D
Dave Airlie 已提交
693
	case VIA_CMDBUF_SPACE:
D
Dave Airlie 已提交
694 695
		while (((tmp_size = via_cmdbuf_space(dev_priv)) < d_siz.size)
		       && count--) {
D
Dave Airlie 已提交
696 697 698 699 700 701 702 703 704 705
			if (!d_siz.wait) {
				break;
			}
		}
		if (!count) {
			DRM_ERROR("VIA_CMDBUF_SPACE timed out.\n");
			ret = DRM_ERR(EAGAIN);
		}
		break;
	case VIA_CMDBUF_LAG:
D
Dave Airlie 已提交
706 707
		while (((tmp_size = via_cmdbuf_lag(dev_priv)) > d_siz.size)
		       && count--) {
D
Dave Airlie 已提交
708 709 710 711 712 713 714 715 716 717 718 719 720 721
			if (!d_siz.wait) {
				break;
			}
		}
		if (!count) {
			DRM_ERROR("VIA_CMDBUF_LAG timed out.\n");
			ret = DRM_ERR(EAGAIN);
		}
		break;
	default:
		ret = DRM_ERR(EFAULT);
	}
	d_siz.size = tmp_size;

722
	DRM_COPY_TO_USER_IOCTL((drm_via_cmdbuf_size_t __user *) data, d_siz,
D
Dave Airlie 已提交
723 724 725
			       sizeof(d_siz));
	return ret;
}
D
Dave Airlie 已提交
726 727

drm_ioctl_desc_t via_ioctls[] = {
728 729 730 731 732 733 734 735 736 737 738 739
	[DRM_IOCTL_NR(DRM_VIA_ALLOCMEM)] = {via_mem_alloc, DRM_AUTH},
	[DRM_IOCTL_NR(DRM_VIA_FREEMEM)] = {via_mem_free, DRM_AUTH},
	[DRM_IOCTL_NR(DRM_VIA_AGP_INIT)] = {via_agp_init, DRM_AUTH|DRM_MASTER},
	[DRM_IOCTL_NR(DRM_VIA_FB_INIT)] = {via_fb_init, DRM_AUTH|DRM_MASTER},
	[DRM_IOCTL_NR(DRM_VIA_MAP_INIT)] = {via_map_init, DRM_AUTH|DRM_MASTER},
	[DRM_IOCTL_NR(DRM_VIA_DEC_FUTEX)] = {via_decoder_futex, DRM_AUTH},
	[DRM_IOCTL_NR(DRM_VIA_DMA_INIT)] = {via_dma_init, DRM_AUTH},
	[DRM_IOCTL_NR(DRM_VIA_CMDBUFFER)] = {via_cmdbuffer, DRM_AUTH},
	[DRM_IOCTL_NR(DRM_VIA_FLUSH)] = {via_flush_ioctl, DRM_AUTH},
	[DRM_IOCTL_NR(DRM_VIA_PCICMD)] = {via_pci_cmdbuffer, DRM_AUTH},
	[DRM_IOCTL_NR(DRM_VIA_CMDBUF_SIZE)] = {via_cmdbuf_size, DRM_AUTH},
	[DRM_IOCTL_NR(DRM_VIA_WAIT_IRQ)] = {via_wait_irq, DRM_AUTH}
D
Dave Airlie 已提交
740 741 742
};

int via_max_ioctl = DRM_ARRAY_SIZE(via_ioctls);