/*     
 **********************************************************************
 *     emuadxmg.c - Address space manager for emu10k1 driver 
 *     Copyright 1999, 2000 Creative Labs, Inc. 
 * 
 ********************************************************************** 
 * 
 *     Date                 Author          Summary of changes 
 *     ----                 ------          ------------------ 
 *     October 20, 1999     Bertrand Lee    base code release 
 * 
 ********************************************************************** 
 * 
 *     This program is free software; you can redistribute it and/or 
 *     modify it under the terms of the GNU General Public License as 
 *     published by the Free Software Foundation; either version 2 of 
 *     the License, or (at your option) any later version. 
 * 
 *     This program is distributed in the hope that it will be useful, 
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of 
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 *     GNU General Public License for more details. 
 * 
 *     You should have received a copy of the GNU General Public 
 *     License along with this program; if not, write to the Free 
 *     Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 
 *     USA. 
 * 
 ********************************************************************** 
 */

#include "hwaccess.h"

/************************************************************************
*
*   int emu10kaddxmgrInit(struct sblive_hw * sb_hw)
*
*   ENTRY
*       sb_hw  -    Pointer to the HWOBJ to init voice manager
*
*   RETURNS
*       Always returns CTSTATUS_SUCCESS
*
*   ABOUT
*       Inits data
*
************************************************************************/
int emu10kaddxmgrInit(struct sblive_hw *sb_hw)
{
	u32 count;

	for (count = 0; count < MAXPAGES; count++) 
	  sb_hw->emu_addrmgr.emupagetable[count].flags = 0;

	/* Mark first page as used */
	/* This page is reserved by the driver */
	sb_hw->emu_addrmgr.emupagetable[0].flags = 0x8001;
	sb_hw->emu_addrmgr.emupagetable[1].flags = MAXPAGES - RESERVED - 1;

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int emu10kaddxmgrExit(struct sblive_hw * sb_hw)
*
*   ENTRY
*       sb_hw  -    Pointer to the HWOBJ to exit
*
*   RETURNS
*       Always returns CTSTATUS_SUCCESS
*
*   ABOUT
*       Exits
*
************************************************************************/
int emu10kaddxmgrExit(struct sblive_hw *sb_hw)
{
	u32 count;

	/* TODO : callback all address owners */

	for (count = 0; count < MAXPAGES; count++)
	  sb_hw->emu_addrmgr.emupagetable[count].flags = 0;
	
	sb_hw->emu_addrmgr.emupagetable[0].flags = MAXPAGES - RESERVED;

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   u32 emu10kaddxmgrMaxContigPages(struct sblive_hw * sb_hw)
*
*   ENTRY
*       sb_hw  -    Pointer to the HWOBJ to check
*
*   RETURNS
*       largest free contiguous block in pages
*
*   ABOUT
*       Gets largest free contiguous block in pages
*
************************************************************************/
u32 emu10kaddxmgrMaxContigPages(struct sblive_hw *sb_hw)
{
	u32 maxpages = 0;
	u16 pageindex = 0;
	struct emu_addrmgr * mgr;

	mgr = &sb_hw->emu_addrmgr;

	while (pageindex < (MAXPAGES - RESERVED - 1)) 
	{
		if (mgr->emupagetable[pageindex].flags & 0x8000) 
		{
			/* This block of pages is in use, jump to the start of the next block */
			pageindex += (mgr->emupagetable[pageindex].flags & 0x7fff);
		} else 
		{ 
			/* Found free block */
			if (mgr->emupagetable[pageindex].flags > (u16) maxpages) 
			  maxpages = mgr->emupagetable[pageindex].flags; /* Block is large enough */
			else 
			  pageindex += mgr->emupagetable[pageindex].flags; /* Block too small - jump to the start of the next block */
		}
	}

	return maxpages;
}


/************************************************************************
*
*   int emu10kaddxmgrAlloc(struct emuaddr_allocdesc * emuaddrAllocDesc,
*                         struct emu_page *  *pemuaddrObj)
*
*   ENTRY
*       emuaddrAllocDesc -
*           Pointer to a struct emuaddr_allocdesc that describes the
*           address space needed.
*
*       pemuaddrObj      -
*           Address to return allocated struct emu_page
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_NOMEMORY (not enough available address space)
*
*   ABOUT
*       Allocates emu address space
*
************************************************************************/
int emu10kaddxmgrAlloc(struct emuaddr_allocdesc *allocdesc, struct emu_page **emu_pageptr)
{
	struct sblive_hw * sb_hw;
	u32 emupageindex = 0;
	u32 pages;
	struct emu_page * pemupagetable;
	unsigned long flags;

	sb_hw = allocdesc->sb_hw;
	pemupagetable = sb_hw->emu_addrmgr.emupagetable;
	*emu_pageptr = NULL;

	/* Convert bytes to pages */
	pages = (allocdesc->size / PAGE_SIZE) +
	    ((allocdesc->size % PAGE_SIZE) ? 1 : 0);

	while (emupageindex < (MAXPAGES - RESERVED - 1)) 
	{
		if (pemupagetable[emupageindex].flags & 0x8000) 
		{
			/* This block of pages is in use, jump to the start of the next block. */
			emupageindex += (pemupagetable[emupageindex].flags & 0x7fff);
		} else 
		{
			/* Found free block */
			if (pemupagetable[emupageindex].flags >= (u16) pages) 
			{
				spin_lock_irqsave(&sb_hw->emu_lock, flags);

				/* Block is large enough */
				
				/* If free block is larger than the block requested
				 * then adjust the size of the block remaining */
				if (pemupagetable[emupageindex].flags > (u16) pages) 
				  pemupagetable[emupageindex + pages].flags = pemupagetable[emupageindex].flags - (u16) pages;
				
				pemupagetable[emupageindex].flags = (u16) (pages | 0x8000); /* Mark block as used */

				/* Return emu address */
				pemupagetable[emupageindex].sb_hw = sb_hw;
				pemupagetable[emupageindex].emustartaddr = emupageindex << 11;
				*emu_pageptr = &pemupagetable[emupageindex];

				spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

				return CTSTATUS_SUCCESS;
			} else 
			{
				/* Block too small, jump to the start of the next block */
				emupageindex += pemupagetable[emupageindex].flags;
			}
		}
	}

	return CTSTATUS_NOMEMORY;
}

/************************************************************************
*
*   int emu10kaddxmgrFree(struct emu_page * emuaddrObj)
*
*   ENTRY
*       emuaddrObj -
*           pointer to a struct emu_page returned
*           by a call to emu10kaddxmgrAlloc()
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Frees a previously allocated emu address space.
*
************************************************************************/
int emu10kaddxmgrFree(struct emu_page *page)
{
	struct sblive_hw * sb_hw;
	u16 origsize = 0;
	u32 pageindex;
	struct emu_page * pemupagetable;
	unsigned long flags;

	sb_hw = page->sb_hw;
	spin_lock_irqsave(&sb_hw->emu_lock, flags);

	pemupagetable = sb_hw->emu_addrmgr.emupagetable;
	pageindex = page->emustartaddr;

	/* Convert Emu address to Emu page number */
	pageindex >>= 11;

	if (pemupagetable[pageindex].flags & 0x8000) 
	{
		/* Block is allocated - mark block as free */
		origsize = pemupagetable[pageindex].flags & 0x7fff;
		pemupagetable[pageindex].flags = origsize;
		pemupagetable[pageindex].emustartaddr = 0;

		/* If next block is free, we concat both blocks */
		if (!(pemupagetable[pageindex + origsize].flags & 0x8000)) 
		  pemupagetable[pageindex].flags += pemupagetable[pageindex + origsize].flags & 0x7fff;
	}
	
	spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

	return origsize;
}
