


#include "rockmacros.h"
#include "defs.h"
#include "cpu-gb.h"
#include "hw.h"
#include "regs.h"
#include "lcd-gb.h"
#include "mem.h"
#include "fastmem.h"


struct hw hw IBSS_ATTR;



/*
 * hw_interrupt changes the virtual interrupt lines included in the
 * specified mask to the values the corresponding bits in i take, and
 * in doing so, raises the appropriate bit of R_IF for any interrupt
 * lines that transition from low to high.
 */

void hw_interrupt(byte i, byte mask)
{
    byte oldif = R_IF;
    i &= 0x1F & mask;
    R_IF |= i & (hw.ilines ^ i);

    /* FIXME - is this correct? not sure the docs understand... */
    if ((R_IF & (R_IF ^ oldif) & R_IE) && cpu.ime) cpu.halt = 0;
    /* if ((i & (hw.ilines ^ i) & R_IE) && cpu.ime) cpu.halt = 0; */
    /* if ((i & R_IE) && cpu.ime) cpu.halt = 0; */
    
    hw.ilines &= ~mask;
    hw.ilines |= i;
}


/*
 * hw_dma performs plain old memory-to-oam dma, the original dmg
 * dma. Although on the hardware it takes a good deal of time, the cpu
 * continues running during this mode of dma, so no special tricks to
 * stall the cpu are necessary.
 */

void hw_dma(byte b)
{
    int i;
    addr a;

    a = ((addr)b) << 8;
    for (i = 0; i < 160; i++, a++)
        lcd.oam.mem[i] = readb(a);
}

void hw_hdma(void)
{
    int cnt;
    addr sa;
    int da;

    sa = ((addr)R_HDMA1 << 8) | (R_HDMA2&0xf0);
    da = 0x8000 | ((int)(R_HDMA3&0x1f) << 8) | (R_HDMA4&0xf0);
    for (cnt=16; cnt>0; cnt--)
        writeb(da++, readb(sa++));
    cpu_timers(16);
    R_HDMA1 = sa >> 8;
    R_HDMA2 = sa & 0xF0;
    R_HDMA3 = 0x1F & (da >> 8);
    R_HDMA4 = da & 0xF0;
    R_HDMA5--;
    hw.hdma--;
}

void hw_hdma_cmd(byte c)
{
    int cnt;
    addr sa;
    int da;

    /* Begin or cancel HDMA */
    if ((hw.hdma|c) & 0x80)
    {
        hw.hdma = c;
        R_HDMA5 = c & 0x7f;
        if ((R_STAT&0x03) == 0x00) hw_hdma();
        return;
    }
    
    /* Perform GDMA */
    sa = ((addr)R_HDMA1 << 8) | (R_HDMA2&0xf0);
    da = 0x8000 | ((int)(R_HDMA3&0x1f) << 8) | (R_HDMA4&0xf0);
    cnt = (((int)c)+1) << 4;
    /* FIXME - this should use cpu time! */
    /*cpu_timers(102 * cnt);*/
    cpu_timers((460>>cpu.speed)+cnt); /*dalias*/
	/*cpu_timers(228 + (16*cnt));*/ /* this should be right according to no$ */
    while (cnt--)
        writeb(da++, readb(sa++));
    R_HDMA1 = sa >> 8;
    R_HDMA2 = sa & 0xF0;
    R_HDMA3 = 0x1F & (da >> 8);
    R_HDMA4 = da & 0xF0;
    R_HDMA5 = 0xFF;
}


/*
 * pad_refresh updates the P1 register from the pad states, generating
 * the appropriate interrupts (by quickly raising and lowering the
 * interrupt line) if a transition has been made.
 */

void pad_refresh(void)
{
    byte oldp1;
    oldp1 = R_P1;
    R_P1 &= 0x30;
    R_P1 |= 0xc0;
    if (!(R_P1 & 0x10))
        R_P1 |= (hw.pad & 0x0F);
    if (!(R_P1 & 0x20))
        R_P1 |= (hw.pad >> 4);
    R_P1 ^= 0x0F;
    if (oldp1 & ~R_P1 & 0x0F)
    {
        hw_interrupt(IF_PAD, IF_PAD);
        hw_interrupt(0, IF_PAD);
    }
}


/*
 * These simple functions just update the state of a button on the
 * pad.
 */

static void pad_press(byte k) ICODE_ATTR;
static void pad_press(byte k)
{
    if (hw.pad & k)
        return;
    hw.pad |= k;
    pad_refresh();
}

static void pad_release(byte k) ICODE_ATTR;
static void pad_release(byte k)
{
    if (!(hw.pad & k))
        return;
    hw.pad &= ~k;
    pad_refresh();
}

void pad_set(byte k, int st)
{
    st ? pad_press(k) : pad_release(k);
}

void hw_reset(void)
{
    hw.ilines = hw.pad = 0;

    memset(ram.hi, 0, sizeof ram.hi);

    R_P1 = 0xFF;
    R_LCDC = 0x91;
    R_BGP = 0xFC;
    R_OBP0 = 0xFF;
    R_OBP1 = 0xFF;
    R_SVBK = 0x01;
    R_HDMA5 = 0xFF;
    R_VBK = 0xFE;
}
