/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* 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, using version 2 of the License.             */
/*                                                                           */
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/
/**
    @file   memory.c
    @brief  Implementation of Itmml.h

@b  Component:  tmml

This is very much a stub implementation, that treats memspace operations
as no-ops, and maps all allocation requests to kmalloc()

*//*
Rev Date        Author      Comments
--------------------------------------------------------------------------------
  6 20070315    burningh    Add cache flush and invalidate implementations
  6 20070926    awest       remove KMALLOC_MAX_SIZE
  7 20070926    awest       add back in io.h
  8 20080207    batelaan    Update file copyright/license text
 10 20080319    steel       Fix cache flush / invalidate
 11 20080417    steel       Tidy cache flush / invalidate
 12 20080829    batelaan    Fix compilation warnings
--------------------------------------------------------------------------------
*/

#include <tmml.h>
#include "common.h"
#include <linux/dma-mapping.h>
#include <asm/io.h>


tmErrorCode_t tmmlInit(void)
{
	return TM_OK;
}
EXPORT_SYMBOL(tmmlInit);

tmErrorCode_t tmmlExit(void)
{
	return TM_OK;
}
EXPORT_SYMBOL(tmmlExit);

tmErrorCode_t tmmlCreate(ptmmlMmspHandle_t pHandle, UInt32 nrOfBytes,
		tmmlMmspCreateFlags_t flags)
{
	static int handleCounter = 0;

	if (!pHandle)
		return TMML_ERR_BAD_PARAMETER;

	*pHandle = ++handleCounter;
	return TM_OK;
}
EXPORT_SYMBOL(tmmlCreate);

tmErrorCode_t tmmlDelete(tmmlMmspHandle_t handle)
{
	return TM_OK;
}
EXPORT_SYMBOL(tmmlDelete);


/* We assume that kmalloc() will magically satisfy almost all alignment
 * request. This usually will be the case, because of how kmalloc() works.
 *
 * In cases when alignment won't be get for free, we will kmalloc more,
 * to store a header and get aligned returned address. On block free we will
 * have to check is block being returned was allocated in such way. However,
 * we expect that very few blocks will require such handling, so checking
 * is not expected to be expensive */

struct allocated_block {
	struct allocated_block *next;
	void *block;
};
static struct allocated_block *head;
static spinlock_t lock = SPIN_LOCK_UNLOCKED;

/* Link new entry to the list */
static void link_entry(void *allocated, void *block)
{
	struct allocated_block *b;

	b = (struct allocated_block *)allocated;
	b->block = block;
	spin_lock(&lock);
	b->next = head;
	head = b;
	spin_unlock(&lock);
}

/* Check which address to give to kfree() for block address;
 * remove entry from list as a side effect */
static void *allocated_address(void *block)
{
	struct allocated_block **b;
	void *ret = block;

	spin_lock(&lock);
	for (b = &head; *b; b = &((*b)->next)) {
		if ((*b)->block == block) {
			ret = (void *)(*b);
			*b = (*b)->next;
			break;
		}
	}
	spin_unlock(&lock);
	return ret;
}

tmErrorCode_t tmmlRetailMalloc(tmmlMmspHandle_t handle, UInt32 nrOfBytes,
		pVoid *ppAddress, tmmlMallocFlags_t flags)
{
	return tmmlRetailMallocAlign(handle, nrOfBytes, ppAddress, flags, 1);
}
EXPORT_SYMBOL(tmmlRetailMalloc);

tmErrorCode_t tmmlRetailMallocAlign(tmmlMmspHandle_t handle, UInt32 nrOfBytes,
		pVoid *ppAddress, tmmlMallocFlags_t flags,
		UInt32 alignment)
{
	void *allocated, *block;
	unsigned int alloc_size, align_mod, align_shift;
	unsigned long tmp1, tmp2;

	BUG_IF_INTERRUPT();
	BUG_IF_NOT_PREEMPTIBLE();

	/* We usually use kmalloc; it will automatically satisfy almost any
	 * sensible alignment wish. In case it won't, we retry with a larger
	 * size, and force alignment. Such blocks have to be tracked, to
	 * give valid pointer at kfree() time. For that, we use first 8
	 * bytes of allocated block: first word stores address of the aligned
	 * block returned to requester, second is used to link all such blocks
	 * together.
	 *
	 * Note that most times the maintained list is expected to be empty,
	 * or contain just several entrues, so checking it should not be
	 * expensive */

	if (!ppAddress || !alignment)
		return TMML_ERR_BAD_PARAMETER;

	/* To support tmmlMallocCacheSet. we need to split alignment
	 * into module and shift. E.g. if alignment is 10110b, module
	 * is 10000b and shift is 00110b */

	for (align_mod = 1;
	     align_mod && (alignment & ~(2 * align_mod - 1));
	     align_mod <<= 1);
	align_shift = alignment & (align_mod - 1);

	/* Disallow non-zero shift if tmmlMallocCacheSet flag not set */
	if (align_shift && !(flags & tmmlMallocCacheSet))
		return TMML_ERR_BAD_PARAMETER;

	/* For zero shift, try just to kmalloc. It will satisfy alignment
	 * in most cases */
	if (!align_shift) {
		block = kmalloc(nrOfBytes, GFP_KERNEL);
		if (unlikely(!block))
			return TMML_ERR_OUT_OF_MEMORY;
		if (likely(((unsigned long)block & (align_mod - 1)) == 0))
			goto block_ready;
		kfree(block);
	}

	/* We have to do manual tracking for this block */

	alloc_size = sizeof(struct allocated_block) + align_mod + nrOfBytes;
	allocated = kmalloc(alloc_size, GFP_KERNEL);

	tmp1 = (unsigned long)allocated + sizeof(struct allocated_block);
	/* calculate address between tmp1 and tmp1 + alloc_mod that has proper
	 * alignment */
	tmp2 = (tmp1 & ~(align_mod - 1)) + align_shift;
	if (tmp2 < tmp1)
		tmp2 += align_mod;
	block = (void *)tmp2;

	link_entry(allocated, block);

block_ready:
	if (flags & tmmlMallocCleared)
		memset(block, 0, nrOfBytes);
	*ppAddress = block;
	return TM_OK;
}
EXPORT_SYMBOL(tmmlRetailMallocAlign);

tmErrorCode_t tmmlFree(pVoid pAddress)
{
	kfree(allocated_address(pAddress));
	return TM_OK;
}
EXPORT_SYMBOL(tmmlFree);


/* define these because tmmlMalloc* macros may be resolved into these */

tmErrorCode_t tmmlDebugMalloc(tmmlMmspHandle_t handle, UInt32 nrOfBytes,
		pVoid *ppAddress, UInt16 bSize, UInt16 aSize,
		String file, Int line, tmmlMallocFlags_t flags)
{
	printk(KERN_WARNING "%s:%d: %s() not supported\n", file, line, __FUNCTION__);
	return tmmlRetailMalloc(handle, nrOfBytes, ppAddress, flags);
}
EXPORT_SYMBOL(tmmlDebugMalloc);


tmErrorCode_t tmmlDebugMallocAlign(tmmlMmspHandle_t handle, UInt32 nrOfBytes,
		pVoid *ppAddress, UInt16 bSize, UInt16 aSize,
		String file, Int line, tmmlMallocFlags_t flags,
		UInt32 alignment)
{
	printk(KERN_WARNING "%s:%d: %s() not supported\n", file, line, __FUNCTION__);
	return tmmlRetailMallocAlign(handle, nrOfBytes, ppAddress, flags, alignment);
}
EXPORT_SYMBOL(tmmlDebugMallocAlign);


tmErrorCode_t
tmmlCacheFlush (
    pVoid  pAddress,
    UInt32 nrOfBytes
    )
{
    dma_cache_wback((unsigned long) pAddress, nrOfBytes);
    return TM_OK;
}
EXPORT_SYMBOL(tmmlCacheFlush);


tmErrorCode_t
tmmlCacheInvalidate (
    pVoid  pAddress,
    UInt32 nrOfBytes
    )
{
    dma_cache_inv((unsigned long) pAddress, nrOfBytes);
    return TM_OK;
}
EXPORT_SYMBOL(tmmlCacheInvalidate);

