/***************************************************************************
*	NAME:  IWMEM.C $Revision: 1.7 $
**	COPYRIGHT:
**	"Copyright (c) 1994,1995 by e-Tek Labs"
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************
* $Log: iwmem.c $
* Revision 1.7  1995/08/02 14:20:25  mleibow
* Added lots of debugging code.
* Revision 1.6  1995/05/03 08:33:33  sdsmith
* Added ROM debugging
* Revision 1.5  1995/04/20 05:07:45  mleibow
* No longer error to have no DRAM on card
* Revision 1.4  1995/04/10 15:46:20  mleibow
* Added ROM initialization
* Revision 1.3  1995/04/10 11:22:44  mleibow
* Added ROM support
* Revision 1.2  1995/02/24 14:01:59  mleibow
* added deinit code
* Revision 1.1  1995/02/23 11:07:13  unknown
* Initial revision
***************************************************************************/
#include <dos.h>
#include "iw.h"
#include "iwl.h"
#include "globals.h"

#define BANK_SIZE_BITS	22

static unsigned short bank;

#ifdef MEM_DEBUG
static unsigned long __far addrs[10240] = {0};

static void add(unsigned long x)
{
    int i;

    for (i=0; i < 10240; i++) {
	if (addrs[i] == 0) {
	    addrs[i] = x;
	    return;
	}
    }
    return;
}

static int remove(unsigned long x)
{
    int i;

    if (x == 0) return(1);
    for (i=0; i < 10240; i++) {
	if (addrs[i] == x) {
	    addrs[i] = 0;
	    return(0);
	}
    }
    return(1);
}
#endif

#define EMPTY_STATUS	0
#define STAT_USED	(1 << 0)

#ifdef MEM_DEBUG
static long total = 0;

int iw_mem_validate(void)
{
    int i;
    long count;
    struct iwl_mem_block m;
    unsigned long addr;

    ENTER;
    count = 0;
    for (i=0; i < 4; i++) {
	if (bank & (1 << i)) {
	    addr = (ULONG)i<<BANK_SIZE_BITS;
	    while (addr != -1L) {
                iwl_read_block(addr, &m);
		count += m.size;
		addr = m.next_block;
	    }
	}
    }
    LEAVE;
    if (total == count) return(1);
    return(0);
}
#endif

/***************************************************************************

FUNCITON DEFINITION:
iwl_write_block - writes a memory control block to DRAM

DESCRIPTION:
The synthesizer DRAM is used to store the memory control blocks which
control the usage of the DRAM.  iwl_write_block writes a memory control
block to DRAM starting at the specified address.

RETURNS: void
*/
void iwl_write_block(
  unsigned long addr,          /* address to start writing DRAM control block */
  struct iwl_mem_block RFAR *m) /* pointer to memory control block to write */
{
//	unsigned char RFAR *mp = (unsigned char RFAR *)m;

    ENTER; /* make sure all bytes get written simultaneously */
    //for (i=0; i < sizeof(struct iwl_mem_block); i++) 
      //iw_poke(addr++, *mp++);
    /* Set up the Local memory I/O address  */
    OS_OUTPORTB(iwl_register_select, SET_DRAM_LOW);
    OS_OUTPORTW(iwl_data_low, (unsigned)addr);
    OS_OUTPORTB(iwl_register_select, SET_DRAM_HIGH);
    OS_OUTPORTB(iwl_data_high, (unsigned char)((addr>>16)&0xFF) );
    os_outs(/*iwl_dram_io,*/ m, sizeof(struct iwl_mem_block));
    LEAVE;
}

/***************************************************************************

FUNCITON DEFINITION:
iwl_read_block - reads a memory control block from DRAM

DESCRIPTION:
The synthesizer DRAM is used to store the memory control blocks which
control the usage of the DRAM.  iwl_read_block reads a memory control
block from DRAM starting at the specified address into a memory 
control structure.

RETURNS: void
*/
void iwl_read_block(
  unsigned long addr,  /* DRAM address of memory control block */
  struct iwl_mem_block RFAR *m) /* address of memory control structure */
{
//	unsigned char RFAR *mp = (unsigned char RFAR *)m;

    ENTER;
    //for (i=0; i < sizeof(struct iwl_mem_block); i++) *mp++ = iw_peek(addr++);
    /* Set up the Local memory I/O address  */
    OS_OUTPORTB(iwl_register_select, SET_DRAM_LOW);
    OS_OUTPORTW(iwl_data_low, (unsigned)addr);
    OS_OUTPORTB(iwl_register_select, SET_DRAM_HIGH);
    OS_OUTPORTB(iwl_data_high, (unsigned char)((addr>>16)&0xFF) );
    os_ins(iwl_dram_io, m, sizeof(struct iwl_mem_block));
    LEAVE;
}

/***************************************************************************

FUNCITON DEFINITION:
iwl_mem_init - initializes memory control blocks for DRAM usage

DESCRIPTION:
iiwl_mem_init initializes the first DRAM memory control block to indicate
that all available DRAM is free for use.  There is at least one DRAM
control block for each bank of DRAM available.

RETURNS: int - IW_OK if operation is successful
               IW_NO_MEMORY if no memory is available on the card

NOTE: this routine is called as part of the autoinit feature of the
      InterWave kernel
*/
int iwl_mem_init(void)
{
    int i, j;
    unsigned long bank_size;
    struct iwl_mem_block m;

    /* set up memory configuration for 16Mb of RAM */
    IWL_INP_W(IW_LMCFI, iwl_oldlmcfi);
    iwl_lmcfi = (iwl_oldlmcfi&~0xEF) | 0x8C;
    IWL_OUT_W(IW_LMCFI, iwl_lmcfi);

    /* Set local memory for auto increment */
    IWL_OUT_B(IW_LMCI, IWL_LMCI_AI);

#ifdef MEM_DEBUG
    iwu_memset(addrs, 0, sizeof(addrs));
    total = 0;
#endif
    /* find out how much memory is out there */
    bank = 0;
    for (i=0; i < 4; i++) {
	bank_size = 0;
	for (j=0; j < 16; j++) {
	    if (iw_good_dram(((ULONG)i<<BANK_SIZE_BITS)+(j*0x40000L))) {
		bank_size += 0x40000L;
	    } else break;
	}
	if (bank_size) {
	    bank |= 1 << i;
#ifdef MEM_DEBUG
	    total += bank_size;
#endif
	    m.size = bank_size;
	    m.next_block = ~0LU;
	    m.prev_block = ~0LU;
	    m.tag	 = 0;
	    m.status = EMPTY_STATUS;
	    iwl_write_block((ULONG)i<<BANK_SIZE_BITS, &m);
	}
    }
//    if (bank == 0) return(IW_NO_MEMORY);
#ifdef DEBUG
    bank = 0;
#endif
    /* kludge -- all of the spare voices point here */
    iw_poke(30, 0x00);
    iw_poke(31, 0x00);
    return(IW_OK);
}
void iwl_mem_deinit(void)
{
    /* restore memory configuration */
    IWL_OUT_W(IW_LMCFI, iwl_oldlmcfi);
}

/***************************************************************************

FUNCITON DEFINITION:
iw_malloc - allocate DRAM for use by the caller

DESCRIPTION:
iw_malloc allocates a number of bytes of DRAM requested by the caller.  The
size requested by the caller is adjusted to be a multiple of 32.  The DRAM
on the the card is search to find the first available chunk of the adjusted
size.  A DRAM memory control block is contructed and stored "in front" on the
requested block of memory.  The new memory control block is added to the 
chain of control blocks in DRAM to control the DRAM usage.

The address of the first byte of DRAM after the memory control block is 
returned to the caller.

RETURNS: unsigned long - address of first byte of DRAM requested by the
                         caller.
*/
unsigned long iw_malloc(
  unsigned long x)  /* number of bytes requested by the caller */
{
	int i;
	unsigned long addr, addr1;
	struct iwl_mem_block m, n;

	/* adjust x so that it is a multiple of 32 */
	x = (x + 31) & 0xFFFFFFE0L;
	/* add room for header */
	x += 32;
	ENTER;
	for (i=0; i < 4; i++) {
	    if (bank & (1 << i)) {
		addr = (ULONG)i<<BANK_SIZE_BITS;
		while (addr != (unsigned long)-1L) {
		    iwl_read_block(addr, &m);
		    if (!(m.status & STAT_USED) && m.size >= x) {
			/* allocate block */
			m.status |= STAT_USED;
			if ((long)(m.size - x) < 64) {
			    iwl_write_block(addr, &m);
			    LEAVE;
#ifdef MEM_DEBUG
			    if (iw_mem_validate() == 0) _asm int 3;
			    add(addr+32);
#endif
			    return(addr+32);
			} else {
			    n.next_block = m.next_block;
			    n.prev_block = addr;
			    n.size = (m.size - x);
			    n.tag = 0;
			    n.status = EMPTY_STATUS;
			    m.next_block = (addr + x);
			    m.size = x;
			    iwl_write_block(addr, &m);
			    iwl_write_block(m.next_block, &n);
			    if (n.next_block != (unsigned long)-1L) {
				addr1 = n.next_block;
				iwl_read_block(addr1, &n);
				n.prev_block = m.next_block;
				iwl_write_block(addr1, &n);
			    }
			    LEAVE;
#ifdef MEM_DEBUG
			    if (iw_mem_validate() == 0) _asm int 3;
			    add(addr+32);
#endif
			    return(addr+32);
			}
		    } else {
			addr = m.next_block;
		    }
		}
	    }
	}
	LEAVE;
#ifdef MEM_DEBUG
	if (iw_mem_validate() == 0) _asm int 3;
#endif
	return(0L);
}

/***************************************************************************

FUNCITON DEFINITION:
iw_free - free DRAM previously allocated

DESCRIPTION:
iw_free releases the block of DRAM indicated by the addres passed in by
the caller.  The memory control block for the given address is removed
from the chain of control blocks.  If the previous control block in the
chain indicates its memory is free, or the next control block in the chain 
indicates its memory is free, the newly freed memory is concatenated 
with the other freed memory.

RETURNS: void
*/
void iw_free(unsigned long x)
{
	struct iwl_mem_block m, p, n;

#ifdef MEM_DEBUG
	if (remove(x)) {
	    _asm int 3;
	    return;
	}
#endif
	/* move address back by size of header */
	x -= 32;
	ENTER;
	iwl_read_block(x, &m);
	if (m.prev_block != (unsigned long)-1L) {
	    iwl_read_block(m.prev_block, &p);
	    if (!(p.status & STAT_USED)) {
		/* previous block is free - concatenate them */
		x = m.prev_block;
		p.size += m.size;
		p.next_block = m.next_block;
		if (p.next_block != (unsigned long)-1L) {
		    /* tell next block that prev block is in different loc */
		    iwl_read_block(p.next_block, &n);
		    n.prev_block = x;
		    iwl_write_block(p.next_block, &n);
		}
		iwl_write_block(x, &p);
		iwl_read_block(x, &m);
	    }
	}
	if (m.next_block != (unsigned long)-1L) {
	    iwl_read_block(m.next_block, &n);
	    if (!(n.status & STAT_USED)) {
		/* next block is free - concatenate them */
		m.size += n.size;
		m.next_block = n.next_block;
		/* now adjust the next->next block's prev pointer */
		if (m.next_block != (unsigned long)-1L) {
		    iwl_read_block(m.next_block, &n);
		    n.prev_block = x;
		    iwl_write_block(m.next_block, &n);
		}
	    }
	}
	m.status &= ~STAT_USED;
	iwl_write_block(x, &m);
	LEAVE;
#ifdef MEM_DEBUG
	if (iw_mem_validate() == 0) _asm int 3;
#endif
}

/***************************************************************************

FUNCITON DEFINITION:
iw_mem_avail - find total amount DRAM available for use

DESCRIPTION:
iw_mem_avail walks through the DRAM control blocks to see which blocks are
free and which are in use.  The amount in the free blocks is totalled and
returned to the caller.

RETURNS: unsigned long - total amount of free DRAM
*/
unsigned long iw_mem_avail(void)
{
	int i;
	unsigned long count;
	struct iwl_mem_block m;
	unsigned long addr;

	ENTER;
	count = 0;
	for (i=0; i < 4; i++) {
	    if (bank & (1 << i)) {
		addr = (ULONG)i<<BANK_SIZE_BITS;
		while (addr != (unsigned long)-1L) {
		    iwl_read_block(addr, &m);
		    if (!(m.status & STAT_USED)) {
			count += m.size;
		    }
		    addr = m.next_block;
		}
	    }
	}
	LEAVE;
	return(count);
}

/***************************************************************************

FUNCITON DEFINITION:
iw_mem_largest_avail - find largest DRAM block available

DESCRIPTION:
iw_mem_largest_avail walks through the DRAM control blocks to see which 
blocks are free and which are in use.  The largest amount in the free blocks 
is returned to the caller.

RETURNS: unsigned long - size of largest block of free DRAM
*/
unsigned long iw_mem_largest_avail(void)
{
	int i;
	unsigned long count;
	struct iwl_mem_block m;
	unsigned long addr;

	ENTER;
	count = 0;
	for (i=0; i < 4; i++) {
	    if (bank & (1 << i)) {
		addr = (ULONG)i<<BANK_SIZE_BITS;
		while (addr != (unsigned long)-1L) {
		    iwl_read_block(addr, &m);
		    if (!(m.status & STAT_USED)) {
			if (m.size > count) {
			    count = m.size;
			}
		    }
		    addr = m.next_block;
		}
	    }
	}
	LEAVE;
	if (count > 32) {
		return(count-32);
	} else {
		return(0);
	}
}

unsigned long iwl_steal_lfo_memory(void)
{
    struct iwl_mem_block m;
    long size;
    int i;

    for (i=0; i < 4; i++) {
	if (bank & (1 << i)) {
	    iwl_read_block((ULONG)i<<BANK_SIZE_BITS, &m);
	    size = m.size;
#ifdef MEM_DEBUG
	    total -= 1024;
#endif
	    size -= 1024;
	    if (size < 0) return((ULONG)-1L);
	    m.size = size;
	    iwl_write_block((ULONG)i<<BANK_SIZE_BITS, &m);
	    return(((ULONG)i<<BANK_SIZE_BITS)+m.size);
	}
    }
    return((ULONG)-1L);
}

#ifdef DEBUG
change_bank(unsigned short b)
{
	bank = b;
}
#endif

