/**
 * TrueReality - N64/dma.c
 * Copyright (C) 1998, 1999 Niki W. Waibel
 *
 * This program is free software; you can redistribute it and/
 * or modify it under the terms of the GNU General Public Li-
 * cence as published by the Free Software Foundation; either
 * version 2 of the Licence, or any later version.
 *
 * This program is distributed in the hope that it will be use-
 * ful, but WITHOUT ANY WARRANTY; without even the implied war-
 * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public Licence for more details.
 *
 * You should have received a copy of the GNU General Public
 * Licence along with this program; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 * USA.
 *
 * Information about me (the author):
 *   Niki W. Waibel, Reichenau 20, 6890 Lustenau, Austria - EUROPE
 *   niki.waibel@gmx.net
**/





#include <stdio.h>
#include <string.h>

#include <type_sizes.h>
#include <file_loader_extern.h>
#include <memory_extern.h>

#include "interrupts.h"
#include "controller.h"
#include "registers.h"
#include "dma.h"





/* the following routines should use real dma! */
/* ... but that is impossible under unix ... maybe we could use a thread? */
/* currently this works :-) */





/*
   write happened to:
   SP_WR_LEN_REG   (0x404000c)
*/

void dma_idmem2dram()
{
        #define DMEM 0x0000
        #define IMEM 0x1000

        int     i;

        WORD    idmem_offset = ((WORD *)mem.sp_reg)[0] & 0xfff;
        int     imem_or_dmem = (((WORD *)mem.sp_reg)[0] & 0x1000);
        
        WORD    dram_offset  = ((WORD *)mem.sp_reg)[1] & 0xffffff;

        WORD    dma_length   = (((WORD *)mem.sp_reg)[3] & 0x00000fff);
#warning The following warnings are because 'count' and 'skip' are not implemented right now (Niki)
        WORD    dma_count    = (((WORD *)mem.sp_reg)[3] & 0x000ff000) >> 12;   /* BUG: not impl */
        WORD    dma_skip     = (((WORD *)mem.sp_reg)[3] & 0xfff00000) >> 20;   /* BUG: not impl */



//        puts("idmem -> dram");
//        printf("%08lx -> %08lx\n", ((WORD *)mem.sp_reg)[0], ((WORD *)mem.sp_reg)[1]);

        for(i=0; i<dma_length; i++)
        {
                if(imem_or_dmem & IMEM)
                        mem.rd_ram[i+dram_offset] = mem.sp_imem[i+idmem_offset];
                else
                        mem.rd_ram[i+dram_offset] = mem.sp_dmem[i+idmem_offset];
        }

        reg.do_or_check_sthg |= RS4300I_DO_SP_INTERRUPT;



        #undef IMEM
        #undef DMEM
        
} /* void dma_idmem2dram() */





/*
   write happened to:
   SP_RD_LEN_REG   (0x4040008)
*/

void dma_dram2idmem()
{
        #define DMEM 0x0000
        #define IMEM 0x1000

        int     i;
        
        WORD    idmem_offset = ((WORD *)mem.sp_reg)[0] & 0xfff;
        int     imem_or_dmem = (((WORD *)mem.sp_reg)[0] & 0x1000);
        
        WORD    dram_offset  = ((WORD *)mem.sp_reg)[1] & 0xffffff;

        WORD    dma_length   = (((WORD *)mem.sp_reg)[2] & 0x00000fff);
        WORD    dma_count    = (((WORD *)mem.sp_reg)[2] & 0x000ff000) >> 12;   /* BUG: not impl */
        WORD    dma_skip     = (((WORD *)mem.sp_reg)[2] & 0xfff00000) >> 20;   /* BUG: not impl */



//        puts("dram -> idmem");
//        printf("%08lx -> %08lx\n", ((WORD *)mem.sp_reg)[1], ((WORD *)mem.sp_reg)[0]);
        
        for(i=0; i<dma_length; i++)
        {
                if(imem_or_dmem & IMEM)
                        mem.sp_imem[i+idmem_offset] = mem.rd_ram[i+dram_offset];
                else
                        mem.sp_dmem[i+idmem_offset] = mem.rd_ram[i+dram_offset];
        }

        reg.do_or_check_sthg |= RS4300I_DO_SP_INTERRUPT;


        //reg.halt = 1;

        #undef IMEM
        #undef DMEM
        
} /* void dma_dram2idmem() */






/*
   write happened to:
   SI_PIF_ADDR_RD64B_REG   (0x4600004)
*/

void dma_pif2dram()
{
        int   i;
        WORD  offset = ((WORD *)mem.si_reg)[0];



//        puts("pif -> dram");
        
        controller_check();
        
        for(i=0; i<64; i++)
        {
                mem.rd_ram[i+offset] = mem.pi_ram[i];
        }

        reg.do_or_check_sthg |= RS4300I_DO_SI_INTERRUPT;


} /* void dma_pif2dram() */





/*
   write happened to:
   SI_PIF_ADDR_WR64B_REG   (0x4600010)
*/

void dma_dram2pif()
{
        int   i;
        WORD  offset = ((WORD *)mem.si_reg)[0];



//        puts("idram -> pif");
        
        for(i=0; i<64; i++)
        {
                mem.pi_ram[i] = mem.pi_ram_w[i] = mem.rd_ram[i+offset];
        }

        //controller_check();

        reg.do_or_check_sthg |= RS4300I_DO_SI_INTERRUPT;

} /* void dma_dram2pif() */





void dma_pi_copy()
{
        int i;
        
        WORD source = ((WORD *)mem.pi_reg)[1];
        WORD dest   = ((WORD *)mem.pi_reg)[0];
        WORD len    = ((WORD *)mem.pi_reg)[3];



//        puts("piram -> dram");
        
        /* BUG:? horrible hack */
        source &= 0x3ffffff;   /* 64MB ... assumed as max rom length */

        if((source+len) > rom.length)
                len = rom.length - source;
        
        
        
        //fputs("dma_pi_copy:\n", stderr);

        for(i=0; i<=len; i++)
        {
                mem.rd_ram[i+dest] = rom.image[i+source];
                //fprintf(stderr, "%02x\n", mem.rd_ram[i+dest]);
        }

        reg.do_or_check_sthg |= RS4300I_DO_PI_INTERRUPT;
        
} /* void dma_pi_copy() */










