/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * 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.
 */

#include <mach/ocmem.h>
#include <mach/ocmem_priv.h>
#include <linux/genalloc.h>

/* All allocator operations are serialized by ocmem driver */

/* The allocators work as follows:
	Constraints:
	1) There is no IOMMU access to OCMEM hence successive allocations
		in the zone must be physically contiguous
	2) Allocations must be freed in reverse order within a zone.

	z->z_start: Fixed pointer to the start of a zone
	z->z_end:   Fixed pointer to the end of a zone

	z->z_head:  Movable pointer to the next free area when growing at head
			Fixed on zones that grow from tail

	z->z_tail:  Movable pointer to the next free area when growing at tail
			Fixed on zones that grow from head

	z->z_free:  Free space in a zone that is updated on an allocation/free

	reserve:    Enable libgenpool to simulate tail allocations
*/

unsigned long allocate_head(struct ocmem_zone *z, unsigned long size)
{

	unsigned long offset;

	offset  = gen_pool_alloc(z->z_pool, size);

	if (!offset)
		return -ENOMEM;

	z->z_head += size;
	z->z_free -= size;
	return offset;
}

unsigned long allocate_tail(struct ocmem_zone *z, unsigned long size)
{
	unsigned long offset;
	unsigned long reserve;
	unsigned long head;

	if (z->z_tail < (z->z_head + size))
		return -ENOMEM;

	reserve = z->z_tail - z->z_head - size;
	if (reserve) {
		head = gen_pool_alloc(z->z_pool, reserve);
		offset = gen_pool_alloc(z->z_pool, size);
		gen_pool_free(z->z_pool, head, reserve);
	} else
		offset = gen_pool_alloc(z->z_pool, size);

	if (!offset)
		return -ENOMEM;

	z->z_tail -= size;
	z->z_free -= size;
	return offset;
}

int free_head(struct ocmem_zone *z, unsigned long offset,
			unsigned long size)
{
	if (offset > z->z_head) {
		pr_err("ocmem: Detected out of order free "
				"leading to fragmentation\n");
		return -EINVAL;
	}
	gen_pool_free(z->z_pool, offset, size);
	z->z_head -= size;
	z->z_free += size;
	return 0;
}

int free_tail(struct ocmem_zone *z, unsigned long offset,
				unsigned long size)
{
	if (offset > z->z_tail) {
		pr_err("ocmem: Detected out of order free "
				"leading to fragmentation\n");
		return -EINVAL;
	}
	gen_pool_free(z->z_pool, offset, size);
	z->z_tail += size;
	z->z_free += size;
	return 0;
}
