/*
 * Copyright (C) 2007 Ben Skeggs.
 * 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 (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 NONINFRINGEMENT.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) 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 USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include "drmP.h"
#include "drm.h"
#include "nouveau_drv.h"
#include "nouveau_dma.h"

int
nouveau_dma_channel_init(struct drm_device *dev)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nouveau_drm_channel *dchan = &dev_priv->channel;
	struct nouveau_gpuobj *gpuobj = NULL;
	struct mem_block *pushbuf;
	int grclass, ret, i;

	DRM_DEBUG("\n");

	pushbuf = nouveau_mem_alloc(dev, 0, 0x8000,
				    NOUVEAU_MEM_FB | NOUVEAU_MEM_MAPPED,
				    (struct drm_file *)-2);
	if (!pushbuf) {
		DRM_ERROR("Failed to allocate DMA push buffer\n");
		return -ENOMEM;
	}

	/* Allocate channel */
	ret = nouveau_fifo_alloc(dev, &dchan->chan, (struct drm_file *)-2,
				 pushbuf, NvDmaFB, NvDmaTT);
	if (ret) {
		DRM_ERROR("Error allocating GPU channel: %d\n", ret);
		return ret;
	}
	DRM_DEBUG("Using FIFO channel %d\n", dchan->chan->id);

	/* Map push buffer */
	drm_core_ioremap(dchan->chan->pushbuf_mem->map, dev);
	if (!dchan->chan->pushbuf_mem->map->handle) {
		DRM_ERROR("Failed to ioremap push buffer\n");
		return -EINVAL;
	}
	dchan->pushbuf = (void*)dchan->chan->pushbuf_mem->map->handle;

	/* Initialise DMA vars */
	dchan->max  = (dchan->chan->pushbuf_mem->size >> 2) - 2;
	dchan->put  = dchan->chan->pushbuf_base >> 2;
	dchan->cur  = dchan->put;
	dchan->free = dchan->max - dchan->cur;

	/* Insert NOPS for NOUVEAU_DMA_SKIPS */
	dchan->free -= NOUVEAU_DMA_SKIPS;
	dchan->push_free = NOUVEAU_DMA_SKIPS;
	for (i=0; i < NOUVEAU_DMA_SKIPS; i++)
		OUT_RING(0);

	/* NV_MEMORY_TO_MEMORY_FORMAT requires a notifier */
	if ((ret = nouveau_notifier_alloc(dchan->chan, NvNotify0, 1,
					  &dchan->notify0_offset))) {
		DRM_ERROR("Error allocating NvNotify0: %d\n", ret);
		return ret;
	}

	/* We use NV_MEMORY_TO_MEMORY_FORMAT for buffer moves */
	if (dev_priv->card_type < NV_50) grclass = NV_MEMORY_TO_MEMORY_FORMAT;
	else                             grclass = NV50_MEMORY_TO_MEMORY_FORMAT;
	if ((ret = nouveau_gpuobj_gr_new(dchan->chan, grclass, &gpuobj))) {
		DRM_ERROR("Error creating NvM2MF: %d\n", ret);
		return ret;
	}

	if ((ret = nouveau_gpuobj_ref_add(dev, dchan->chan, NvM2MF,
					  gpuobj, NULL))) {
		DRM_ERROR("Error referencing NvM2MF: %d\n", ret);
		return ret;
	}
	dchan->m2mf_dma_source = NvDmaFB;
	dchan->m2mf_dma_destin = NvDmaFB;

	BEGIN_RING(NvSubM2MF, NV_MEMORY_TO_MEMORY_FORMAT_NAME, 1);
	OUT_RING  (NvM2MF);
	BEGIN_RING(NvSubM2MF, NV_MEMORY_TO_MEMORY_FORMAT_SET_DMA_NOTIFY, 1);
	OUT_RING  (NvNotify0);
	BEGIN_RING(NvSubM2MF, NV_MEMORY_TO_MEMORY_FORMAT_SET_DMA_SOURCE, 2);
	OUT_RING  (dchan->m2mf_dma_source);
	OUT_RING  (dchan->m2mf_dma_destin);
	FIRE_RING();

	return 0;
}

void
nouveau_dma_channel_takedown(struct drm_device *dev)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nouveau_drm_channel *dchan = &dev_priv->channel;

	DRM_DEBUG("\n");

	if (dchan->chan) {
		nouveau_fifo_free(dchan->chan);
		dchan->chan = NULL;
	}
}

#define READ_GET() ((NV_READ(dchan->chan->get) -                               \
		    dchan->chan->pushbuf_base) >> 2)
#define WRITE_PUT(val) do {                                                    \
	NV_WRITE(dchan->chan->put,                                             \
		 ((val) << 2) + dchan->chan->pushbuf_base);                    \
} while(0)

int
nouveau_dma_wait(struct drm_device *dev, int size)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nouveau_drm_channel *dchan = &dev_priv->channel;
	uint32_t get;

	while (dchan->free < size) {
		get = READ_GET();

		if (dchan->put >= get) {
			dchan->free = dchan->max - dchan->cur;

			if (dchan->free < size) {
				dchan->push_free = 1;
				OUT_RING(0x20000000|dchan->chan->pushbuf_base);
				if (get <= NOUVEAU_DMA_SKIPS) {
					/*corner case - will be idle*/
					if (dchan->put <= NOUVEAU_DMA_SKIPS)
						WRITE_PUT(NOUVEAU_DMA_SKIPS + 1);

					do {
						get = READ_GET();
					} while (get <= NOUVEAU_DMA_SKIPS);
				}

				WRITE_PUT(NOUVEAU_DMA_SKIPS);
				dchan->cur  = dchan->put = NOUVEAU_DMA_SKIPS;
				dchan->free = get - (NOUVEAU_DMA_SKIPS + 1);
			}
		} else {
			dchan->free = get - dchan->cur - 1;
		}
	}

	return 0;
}