/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  EMU10K1 memory page allocation (PTB area)
 *  EMU10K1 chip can handle only 32MByte of the memory at the same time.
 *
 *
 *   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 "driver.h"
#include "emu10k1.h"
#include "dma.h"

/*
 *  page allocation
 */

typedef struct stru_emu10k1_page_range emu10k1_page_range_t;

struct stru_emu10k1_page_range {
	int first_page;		/* first page */
	int last_page;		/* last page */
	void *pages;		/* allocated pages */
	emu10k1_page_range_t *next;
};

static void snd_emu10k1_fill_ptb(emu10k1_t *emu, emu10k1_page_range_t *range)
{
	int page;
	char *ptr = (char *)virt_to_bus(range->pages);
	
	for (page = range->first_page; page <= range->last_page; page++) {
		emu->ptb_pages[page] = ((unsigned int)ptr << 1) | page;
		ptr += 4096;
	}
}

static void snd_emu10k1_empty_ptb(emu10k1_t *emu, emu10k1_page_range_t *range)
{
	int page;
	unsigned int ptr = (unsigned int)virt_to_bus(emu->silent_page) << 1;

	for (page = range->first_page; page <= range->last_page; page++)
		emu->ptb_pages[page] = ptr | page;
}

int snd_emu10k1_ptb_alloc(emu10k1_t *emu, void *pages, unsigned long size, unsigned int *raddr)
{
	emu10k1_page_range_t *range, *nrange;
	unsigned long psize;
	int tmp;

	if (raddr == NULL)
		return -EINVAL;
	*raddr = 0;
	if (emu == NULL)
		return -EINVAL;
	if (size <= 0 || size >= MAXPAGES * 4096)
		return -EINVAL;
	if ((long)virt_to_bus(pages) & ~0x7fffffffUL)
		return -EINVAL;
	nrange = (emu10k1_page_range_t *)snd_calloc(sizeof(*nrange));
	if (nrange == NULL)
		return -EINVAL;
	snd_mutex_lock(emu, ptb);
	nrange->pages = pages;
	range = (emu10k1_page_range_t *)emu->ptb_page_alloc;
	psize = (size + 4095) >> 12;
	if (range == NULL) {
		nrange->first_page = 0;
		nrange->last_page = psize - 1;
		snd_emu10k1_fill_ptb(emu, nrange);
		emu->ptb_page_alloc = nrange;
		snd_mutex_unlock(emu, ptb);
		// printk("memalloc - raddr = 0x%x, size = %li\n", *raddr, size);
		return 0;
	}
	while (range) {
		if (range->next == NULL) {
			tmp = (MAXPAGES-1) - (range->last_page + 1);
		} else {
			tmp = (range->next->first_page - 1) - range->last_page;
		}
		if (tmp < psize) {
			range = range->next;
			continue;
		}
		nrange->first_page = range->last_page + 1;
		nrange->last_page = (nrange->first_page + psize) - 1;
		snd_emu10k1_fill_ptb(emu, nrange);
		nrange->next = range->next;
		range->next = nrange;
		*raddr = nrange->first_page << 12;
		snd_mutex_unlock(emu, ptb);
		// printk("memalloc - raddr = 0x%x, size = %li\n", *raddr, size);
		return 0;
	}
	snd_mutex_unlock(emu, ptb);
	snd_free(nrange, sizeof(*nrange));
      	return -ENOMEM;
}

int snd_emu10k1_ptb_free(emu10k1_t *emu, void *obj, unsigned long *size)
{
	emu10k1_page_range_t *range, *prange;

	if (size)
		*size = 0;
	if (emu == NULL)
		return -EINVAL;
	if (obj == NULL) {
		snd_printd("snd_emu10k1_free: NULL free?\n");
		return -EINVAL;
	}
	snd_mutex_lock(emu, ptb);
	range = (emu10k1_page_range_t *)emu->ptb_page_alloc;
	if (range == NULL)
		goto __error;
 	if (range->pages == obj) {
 		emu->ptb_page_alloc = range->next;
 	} else {
 		prange = range;
 		while (prange->next && prange->next->pages != obj)
 			prange = prange->next;
 		if (prange->next == NULL)
 			goto __error;
 		range = prange->next;
 		prange->next = range->next;
 	}
 	if (size)
		*size = ((range->last_page - range->first_page) + 1) * 4096;
	snd_emu10k1_empty_ptb(emu, range);
	snd_free(range, sizeof(*range));
	snd_mutex_unlock(emu, ptb);
	return 0;
      __error:
	snd_mutex_unlock(emu, ptb);
      	snd_printd("snd_emu10k1_free: obj = 0x%lx doesn't exist\n", (long)obj);	
	return -ENOENT;
}

void *snd_emu10k1_synth_malloc(emu10k1_t *emu, unsigned long size, unsigned int *raddr)
{
	void *pages;

	/* NOTE: The top memory limit for SB Live cards is 2GB!!! FIXME */
	if ((pages = snd_malloc_pages(size, NULL, 1)) == NULL)
		return NULL;
	if (snd_emu10k1_ptb_alloc(emu, pages, size, raddr) < 0) {
		snd_free_pages(NULL, pages, size);
		return NULL;
	}
	return pages;
}

void snd_emu10k1_synth_free(emu10k1_t *emu, void *obj)
{
	unsigned long size;

	if (snd_emu10k1_ptb_free(emu, obj, &size) < 0)
		return;
	snd_free_pages(NULL, obj, size);
}

