/****************************************************************************
 * Copyright (C) 2003-2006 by XGI Technology, Taiwan.
 *
 * 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 on 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 NON-INFRINGEMENT.  IN NO EVENT SHALL
 * XGI 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 "xgi_drv.h"
#include "xgi_regs.h"
#include "xgi_misc.h"
#include "xgi_cmdlist.h"

static void xgi_emit_flush(struct xgi_info * info, bool stop);
static void xgi_emit_nop(struct xgi_info * info);
static unsigned int get_batch_command(enum xgi_batch_type type);
static void triggerHWCommandList(struct xgi_info * info);
static void xgi_cmdlist_reset(struct xgi_info * info);


/**
 * Graphic engine register (2d/3d) acessing interface
 */
static inline void dwWriteReg(struct drm_map * map, u32 addr, u32 data)
{
#ifdef XGI_MMIO_DEBUG
	DRM_INFO("mmio_map->handle = 0x%p, addr = 0x%x, data = 0x%x\n",
		 map->handle, addr, data);
#endif
	DRM_WRITE32(map, addr, data);
}


int xgi_cmdlist_initialize(struct xgi_info * info, size_t size,
			   struct drm_file * filp)
{
	struct xgi_mem_alloc mem_alloc = {
		.location = XGI_MEMLOC_NON_LOCAL,
		.size = size,
	};
	int err;

	err = xgi_alloc(info, &mem_alloc, filp);
	if (err) {
		return err;
	}

	info->cmdring.ptr = xgi_find_pcie_virt(info, mem_alloc.hw_addr);
	info->cmdring.size = mem_alloc.size;
	info->cmdring.ring_hw_base = mem_alloc.hw_addr;
	info->cmdring.last_ptr = NULL;
	info->cmdring.ring_offset = 0;

	return 0;
}


/**
 * get_batch_command - Get the command ID for the current begin type.
 * @type: Type of the current batch
 *
 * See section 3.2.2 "Begin" (page 15) of the 3D SPG.
 *
 * This function assumes that @type is on the range [0,3].
 */
unsigned int get_batch_command(enum xgi_batch_type type)
{
	static const unsigned int ports[4] = {
		0x30 >> 2, 0x40 >> 2, 0x50 >> 2, 0x20 >> 2
	};

	return ports[type];
}


int xgi_submit_cmdlist(struct drm_device * dev, void * data,
		       struct drm_file * filp)
{
	struct xgi_info *const info = dev->dev_private;
	const struct xgi_cmd_info *const pCmdInfo =
		(struct xgi_cmd_info *) data;
	const unsigned int cmd = get_batch_command(pCmdInfo->type);
	u32 begin[4];


	begin[0] = (cmd << 24) | BEGIN_VALID_MASK
		| (BEGIN_BEGIN_IDENTIFICATION_MASK & info->next_sequence);
	begin[1] = BEGIN_LINK_ENABLE_MASK | pCmdInfo->size;
	begin[2] = pCmdInfo->hw_addr >> 4;
	begin[3] = 0;

	if (info->cmdring.last_ptr == NULL) {
		const unsigned int portOffset = BASE_3D_ENG + (cmd << 2);


		/* Enable PCI Trigger Mode
		 */
		dwWriteReg(info->mmio_map,
			   BASE_3D_ENG + M2REG_AUTO_LINK_SETTING_ADDRESS,
			   (M2REG_AUTO_LINK_SETTING_ADDRESS << 22) |
			   M2REG_CLEAR_COUNTERS_MASK | 0x08 |
			   M2REG_PCI_TRIGGER_MODE_MASK);

		dwWriteReg(info->mmio_map,
			   BASE_3D_ENG + M2REG_AUTO_LINK_SETTING_ADDRESS,
			   (M2REG_AUTO_LINK_SETTING_ADDRESS << 22) | 0x08 |
			   M2REG_PCI_TRIGGER_MODE_MASK);


		/* Send PCI begin command
		 */
		dwWriteReg(info->mmio_map, portOffset,      begin[0]);
		dwWriteReg(info->mmio_map, portOffset +  4, begin[1]);
		dwWriteReg(info->mmio_map, portOffset +  8, begin[2]);
		dwWriteReg(info->mmio_map, portOffset + 12, begin[3]);
	} else {
		DRM_DEBUG("info->cmdring.last_ptr != NULL\n");

		if (pCmdInfo->type == BTYPE_3D) {
			xgi_emit_flush(info, false);
		}

		info->cmdring.last_ptr[1] = cpu_to_le32(begin[1]);
		info->cmdring.last_ptr[2] = cpu_to_le32(begin[2]);
		info->cmdring.last_ptr[3] = cpu_to_le32(begin[3]);
		DRM_WRITEMEMORYBARRIER();
		info->cmdring.last_ptr[0] = cpu_to_le32(begin[0]);

		triggerHWCommandList(info);
	}

	info->cmdring.last_ptr = xgi_find_pcie_virt(info, pCmdInfo->hw_addr);
	return 0;
}


/*
    state:      0 - console
                1 - graphic
                2 - fb
                3 - logout
*/
int xgi_state_change(struct xgi_info * info, unsigned int to,
		     unsigned int from)
{
#define STATE_CONSOLE   0
#define STATE_GRAPHIC   1
#define STATE_FBTERM    2
#define STATE_LOGOUT    3
#define STATE_REBOOT    4
#define STATE_SHUTDOWN  5

	if ((from == STATE_GRAPHIC) && (to == STATE_CONSOLE)) {
		DRM_INFO("Leaving graphical mode (probably VT switch)\n");
	} else if ((from == STATE_CONSOLE) && (to == STATE_GRAPHIC)) {
		DRM_INFO("Entering graphical mode (probably VT switch)\n");
		xgi_cmdlist_reset(info);
	} else if ((from == STATE_GRAPHIC)
		   && ((to == STATE_LOGOUT)
		       || (to == STATE_REBOOT)
		       || (to == STATE_SHUTDOWN))) {
		DRM_INFO("Leaving graphical mode (probably X shutting down)\n");
	} else {
		DRM_ERROR("Invalid state change.\n");
		return -EINVAL;
	}

	return 0;
}


int xgi_state_change_ioctl(struct drm_device * dev, void * data,
			   struct drm_file * filp)
{
	struct xgi_state_info *const state =
		(struct xgi_state_info *) data;
	struct xgi_info *info = dev->dev_private;


	return xgi_state_change(info, state->_toState, state->_fromState);
}


void xgi_cmdlist_reset(struct xgi_info * info)
{
	info->cmdring.last_ptr = NULL;
	info->cmdring.ring_offset = 0;
}


void xgi_cmdlist_cleanup(struct xgi_info * info)
{
	if (info->cmdring.ring_hw_base != 0) {
		/* If command lists have been issued, terminate the command
		 * list chain with a flush command.
		 */
		if (info->cmdring.last_ptr != NULL) {
			xgi_emit_flush(info, false);
			xgi_emit_nop(info);
		}

		xgi_waitfor_pci_idle(info);

		(void) memset(&info->cmdring, 0, sizeof(info->cmdring));
	}
}

static void triggerHWCommandList(struct xgi_info * info)
{
	static unsigned int s_triggerID = 1;

	dwWriteReg(info->mmio_map,
		   BASE_3D_ENG + M2REG_PCI_TRIGGER_REGISTER_ADDRESS,
		   0x05000000 + (0x0ffff & s_triggerID++));
}


/**
 * Emit a flush to the CRTL command stream.
 * @info XGI info structure
 *
 * This function assumes info->cmdring.ptr is non-NULL.
 */
void xgi_emit_flush(struct xgi_info * info, bool stop)
{
	const u32 flush_command[8] = {
		((0x10 << 24)
		 | (BEGIN_BEGIN_IDENTIFICATION_MASK & info->next_sequence)),
		BEGIN_LINK_ENABLE_MASK | (0x00004),
		0x00000000, 0x00000000,

		/* Flush the 2D engine with the default 32 clock delay.
		 */
		M2REG_FLUSH_ENGINE_COMMAND | M2REG_FLUSH_2D_ENGINE_MASK,
		M2REG_FLUSH_ENGINE_COMMAND | M2REG_FLUSH_2D_ENGINE_MASK,
		M2REG_FLUSH_ENGINE_COMMAND | M2REG_FLUSH_2D_ENGINE_MASK,
		M2REG_FLUSH_ENGINE_COMMAND | M2REG_FLUSH_2D_ENGINE_MASK,
	};
	const unsigned int flush_size = sizeof(flush_command);
	u32 *batch_addr;
	u32 hw_addr;
	unsigned int i;


	/* check buf is large enough to contain a new flush batch */
	if ((info->cmdring.ring_offset + flush_size) >= info->cmdring.size) {
		info->cmdring.ring_offset = 0;
	}

	hw_addr = info->cmdring.ring_hw_base
		+ info->cmdring.ring_offset;
	batch_addr = info->cmdring.ptr
		+ (info->cmdring.ring_offset / 4);

	for (i = 0; i < (flush_size / 4); i++) {
		batch_addr[i] = cpu_to_le32(flush_command[i]);
	}

	if (stop) {
		*batch_addr |= cpu_to_le32(BEGIN_STOP_STORE_CURRENT_POINTER_MASK);
	}

	info->cmdring.last_ptr[1] = cpu_to_le32(BEGIN_LINK_ENABLE_MASK | (flush_size / 4));
	info->cmdring.last_ptr[2] = cpu_to_le32(hw_addr >> 4);
	info->cmdring.last_ptr[3] = 0;
	DRM_WRITEMEMORYBARRIER();
	info->cmdring.last_ptr[0] = cpu_to_le32((get_batch_command(BTYPE_CTRL) << 24)
		| (BEGIN_VALID_MASK));

	triggerHWCommandList(info);

	info->cmdring.ring_offset += flush_size;
	info->cmdring.last_ptr = batch_addr;
}


/**
 * Emit an empty command to the CRTL command stream.
 * @info XGI info structure
 *
 * This function assumes info->cmdring.ptr is non-NULL.  In addition, since
 * this function emits a command that does not have linkage information,
 * it sets info->cmdring.ptr to NULL.
 */
void xgi_emit_nop(struct xgi_info * info)
{
	info->cmdring.last_ptr[1] = cpu_to_le32(BEGIN_LINK_ENABLE_MASK
		| (BEGIN_BEGIN_IDENTIFICATION_MASK & info->next_sequence));
	info->cmdring.last_ptr[2] = 0;
	info->cmdring.last_ptr[3] = 0;
	DRM_WRITEMEMORYBARRIER();
	info->cmdring.last_ptr[0] = cpu_to_le32((get_batch_command(BTYPE_CTRL) << 24)
		| (BEGIN_VALID_MASK));

	triggerHWCommandList(info);

	info->cmdring.last_ptr = NULL;
}


void xgi_emit_irq(struct xgi_info * info)
{
	if (info->cmdring.last_ptr == NULL)
		return;

	xgi_emit_flush(info, true);
}
