// license:MAME
// copyright-holders:Angelo Salese, Miodrag Milanovic
/***************************************************************************

    Nintendo Virtual Boy

    driver by Miodrag Milanovic & Angelo Salese

    Great info at http://www.goliathindustries.com/vb/
    and http://www.vr32.de/modules/dokuwiki/doku.php?

    TODO:
    - sound is way incomplete
    - various timing issues (irq & events aren't known)
    - 3dtetris: missing gfxs on gameplay (writes to framebuffer)
    - boundh: game is way too fast
    - galactic: ball goes out of bounds sometimes?
    - golf: missing gfxs on gameplay (writes to framebuffer)
    - marioten: title screen logo is misplaced if Mario completes his animation
    - nesterfb: once that you hit the pins, animation phase takes a while to start
    - redalarm: gameplay doesn't work
    - spaceinv: Taito logo only if you press the button, framebuffer?
    - spaceinv: missing shots
    - vlab: doesn't boot (irq issue?)
    - wariolnd: brightness gets suddently darker during intro.

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

#include "emu.h"
#include "cpu/v810/v810.h"
#include "imagedev/cartslot.h"
#include "audio/vboy.h"
#include "vboy.lh"

#define READ_BGMAP(bgoffs) m_bgmap[(bgoffs) & 0xffff]
#define READ_WORLD(wldoffs)   READ_BGMAP((0x1d800 >> 1) + wldoffs)
#define READ_COLTAB1(wldoffs) READ_BGMAP((0x1dc00 >> 1) + wldoffs)
#define READ_COLTAB2(wldoffs) READ_BGMAP((0x1de00 >> 1) + wldoffs)
#define READ_OBJECTS(wldoffs) READ_BGMAP((0x1e000 >> 1) + wldoffs)

#define WRITE_OVR_TEMPDRAW_MAP(woffs, wdat) m_ovr_tempdraw_map[(woffs) & 0x3f] = wdat;
#define READ_OVR_TEMPDRAW_MAP(roffs) m_ovr_tempdraw_map[(roffs) & 0x3f];

#define READ_FONT(roffs) m_font[(roffs)&0x1ffff]

// bit of magic here, we also write pre-flipped copies of the data to extra ram we've allocated
// to simplify the draw loop (we can just pass the flip / unused bits as the upper character bits)
// (all TILE words are in the format of ccxy -ttt tttt tttt
//   where 'c' = palette, 'x/y' are flips, '-' is unused(?) and 't' is your basic tile number

#define WRITE_FONT(woffs) \
	COMBINE_DATA(&m_font[woffs]); /* normal */ \
	UINT16 dat = m_font[woffs]; \
	m_font[((woffs) + 0x4000)] = dat;     /* normal */ \
	m_font[((woffs) + 0x8000) ^ 7] = dat; /* flip y */ \
	m_font[((woffs) + 0xc000) ^ 7] = dat; /* flip y */ \
	dat = BITSWAP16(dat,1,0,3,2,5,4,7,6,9,8,11,10,13,12,15,14);  \
	m_font[((woffs) + 0x10000)] = dat;     /* flip x */ \
	m_font[((woffs) + 0x14000)] = dat;     /* flip x */ \
	m_font[((woffs) + 0x18000) ^ 7] = dat; /* flip x+y */ \
	m_font[((woffs) + 0x1c000) ^ 7] = dat; /* flip x+y */


/* FIXME: most if not all of these must be UINT8 */
struct vboy_regs_t
{
	UINT32 lpc, lpc2, lpt, lpr;
	UINT32 khb, klb;
	UINT8 thb, tlb;
	UINT32 tcr, wcr, kcr;
};

struct vip_regs_t
{
	UINT16 INTPND;
	UINT16 INTENB;
	UINT16 DPSTTS;
	UINT16 DPCTRL;
	UINT16 BRTA;
	UINT16 BRTB;
	UINT16 BRTC;
	UINT16 REST;
	UINT16 FRMCYC;
	UINT16 CTA;
	UINT16 XPSTTS;
	UINT16 XPCTRL;
	UINT16 VER;
	UINT16 SPT[4];
	UINT16 GPLT[4];
	UINT16 JPLT[4];
	UINT16 BKCOL;
};

struct vboy_timer_t
{
	UINT16 count;
	UINT16 latch;
};

struct vboy_timer_t;

class vboy_state : public driver_device
{
public:
	vboy_state(const machine_config &mconfig, device_type type, const char *tag)
		: driver_device(mconfig, type, tag),
			m_maintimer(*this, "timer_main"),
			m_maincpu(*this, "maincpu"),
		m_palette(*this, "palette")
	{
		m_vip_regs.INTPND = 0;
		m_vip_regs.INTENB = 0;
		m_vip_regs.DPSTTS = 0;
		m_vip_regs.DPCTRL = 0;
		m_vip_regs.BRTA = 0;
		m_vip_regs.BRTB = 0;
		m_vip_regs.BRTC = 0;
		m_vip_regs.REST = 0;
		m_vip_regs.FRMCYC = 0;
		m_vip_regs.CTA = 0;
		m_vip_regs.XPSTTS = 0;
		m_vip_regs.XPCTRL = 0;
		m_vip_regs.VER = 0;
		m_vip_regs.SPT[0] = 0;
		m_vip_regs.SPT[1] = 0;
		m_vip_regs.SPT[2] = 0;
		m_vip_regs.SPT[3] = 0;
		m_vip_regs.GPLT[0] = 0;
		m_vip_regs.GPLT[1] = 0;
		m_vip_regs.GPLT[2] = 0;
		m_vip_regs.GPLT[3] = 0;
		m_vip_regs.JPLT[0] = 0;
		m_vip_regs.JPLT[1] = 0;
		m_vip_regs.JPLT[2] = 0;
		m_vip_regs.JPLT[3] = 0;
		m_vip_regs.BKCOL = 0;

		m_vboy_regs.lpc = 0;
		m_vboy_regs.lpc2 = 0;
		m_vboy_regs.lpt = 0;
		m_vboy_regs.lpr = 0;
		m_vboy_regs.khb = 0;
		m_vboy_regs.klb = 0;
		m_vboy_regs.thb = 0;
		m_vboy_regs.tlb = 0;
		m_vboy_regs.tcr = 0;
		m_vboy_regs.wcr = 0;
		m_vboy_regs.kcr = 0x80;

		m_vboy_timer.count = 0;
		m_vboy_timer.latch = 0;
	}

	required_device<timer_device> m_maintimer;
	required_device<cpu_device> m_maincpu;
	required_device<palette_device> m_palette;

	DECLARE_READ32_MEMBER(io_r);
	DECLARE_WRITE32_MEMBER(io_w);
	DECLARE_READ16_MEMBER(vip_r);
	DECLARE_WRITE16_MEMBER(vip_w);
	DECLARE_WRITE16_MEMBER(vboy_font0_w);
	DECLARE_WRITE16_MEMBER(vboy_font1_w);
	DECLARE_WRITE16_MEMBER(vboy_font2_w);
	DECLARE_WRITE16_MEMBER(vboy_font3_w);
	DECLARE_READ16_MEMBER(vboy_font0_r);
	DECLARE_READ16_MEMBER(vboy_font1_r);
	DECLARE_READ16_MEMBER(vboy_font2_r);
	DECLARE_READ16_MEMBER(vboy_font3_r);
	DECLARE_WRITE16_MEMBER(vboy_bgmap_w);
	DECLARE_READ16_MEMBER(vboy_bgmap_r);
	DECLARE_READ8_MEMBER(vboy_lfb0_r);
	DECLARE_READ8_MEMBER(vboy_lfb1_r);
	DECLARE_READ8_MEMBER(vboy_rfb0_r);
	DECLARE_READ8_MEMBER(vboy_rfb1_r);
	DECLARE_WRITE8_MEMBER(vboy_lfb0_w);
	DECLARE_WRITE8_MEMBER(vboy_lfb1_w);
	DECLARE_WRITE8_MEMBER(vboy_rfb0_w);
	DECLARE_WRITE8_MEMBER(vboy_rfb1_w);
	UINT16 *m_font;
	UINT16 *m_bgmap;
	UINT8 *m_l_frame_0;
	UINT8 *m_l_frame_1;
	UINT8 *m_r_frame_0;
	UINT8 *m_r_frame_1;
	vboy_regs_t m_vboy_regs;
	vip_regs_t m_vip_regs;
	vboy_timer_t m_vboy_timer;
	INT32 *m_ovr_tempdraw_map;
	UINT16 m_frame_count;
	UINT8 m_displayfb;
	UINT8 m_drawfb;
	UINT8 m_row_num;
	attotime m_input_latch_time;
	void m_timer_tick(void);
	void m_scanline_tick(int scanline, UINT8 screen_type);
	void m_set_irq(UINT16 irq_vector);
	UINT8 *m_nvptr;
	UINT32 m_vboy_sram[0x10000/4];
	device_t *m_nvimage;
	DECLARE_READ32_MEMBER(sram_r);
	DECLARE_WRITE32_MEMBER(sram_w);

	void put_obj(bitmap_ind16 &bitmap, const rectangle &cliprect, int x, int y, UINT16 code, UINT8 pal);
	void fill_ovr_char(UINT16 code, UINT8 pal);
	INT8 get_bg_map_pixel(int num, int xpos, int ypos);
	void draw_bg_map(bitmap_ind16 &bitmap, const rectangle &cliprect, UINT16 param_base, int mode, int gx, int gp, int gy, int mx, int mp, int my,int h, int w,
											UINT16 x_mask, UINT16 y_mask, UINT8 ovr, bool right, int bg_map_num);
	void draw_affine_map(bitmap_ind16 &bitmap, const rectangle &cliprect, UINT16 param_base, int gx, int gp, int gy, int h, int w,
												UINT16 x_mask, UINT16 y_mask, UINT8 ovr, bool right, int bg_map_num);
	UINT8 display_world(int num, bitmap_ind16 &bitmap, const rectangle &cliprect, bool right, int &cur_spt);
	void m_set_brightness(void);
	virtual void machine_start();
	virtual void machine_reset();
	virtual void video_start();
	DECLARE_PALETTE_INIT(vboy);
	UINT32 screen_update_vboy_left(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
	UINT32 screen_update_vboy_right(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
	TIMER_DEVICE_CALLBACK_MEMBER(timer_main_tick);
	TIMER_DEVICE_CALLBACK_MEMBER(timer_pad_tick);
	TIMER_DEVICE_CALLBACK_MEMBER(vboy_scanlineL);
	TIMER_DEVICE_CALLBACK_MEMBER(vboy_scanlineR);
	void vboy_machine_stop();
	DECLARE_DEVICE_IMAGE_LOAD_MEMBER(vboy_cart);
};


void vboy_state::video_start()
{
	//int i;

	// Allocate memory for temporary screens
	m_ovr_tempdraw_map = auto_alloc_array_clear(machine(), INT32, 0x40);

	// Allocate memory for framebuffers
	m_l_frame_0 = auto_alloc_array_clear(machine(), UINT8, 0x6000);
	m_l_frame_1 = auto_alloc_array_clear(machine(), UINT8, 0x6000);
	m_r_frame_0 = auto_alloc_array_clear(machine(), UINT8, 0x6000);
	m_r_frame_1 = auto_alloc_array_clear(machine(), UINT8, 0x6000);

	m_font  = auto_alloc_array_clear(machine(), UINT16, (0x8000 >> 1)*4 * 2);
	m_bgmap = auto_alloc_array(machine(), UINT16, 0x20000 >> 1);
	memset(m_bgmap, 0, sizeof(UINT16) * (0x20000 >> 1));
}

void vboy_state::put_obj(bitmap_ind16 &bitmap, const rectangle &cliprect, int x, int y, UINT16 code, UINT8 pal)
{
	UINT16 data;
	UINT8 yi, xi, dat, col;

	for (yi = 0; yi < 8; yi++)
	{
		data = READ_FONT(code * 8 + yi);

		for (xi = 0; xi < 8; xi++)
		{
			int res_x,res_y;

			dat = ((data >> (xi << 1)) & 0x03);

			res_x = x + xi;
			res_y = y + yi;

			col = (pal >> (dat*2)) & 3;

			if (dat)
			{
				if (cliprect.contains(res_x, res_y))
					bitmap.pix16((res_y), (res_x)) = m_palette->pen(col);
			}
		}
	}
}



void vboy_state::fill_ovr_char(UINT16 code, UINT8 pal)
{
	UINT16 data;
	UINT8 yi, xi, dat;
	int col;

	for (yi = 0; yi < 8; yi++)
	{
		data = READ_FONT(code * 8 + yi);

		for (xi = 0; xi < 8; xi++)
		{
			dat = ((data >> (xi << 1)) & 0x03);
			col = (pal >> (dat*2)) & 3;

			if(dat == 0)
				col = -1;

			WRITE_OVR_TEMPDRAW_MAP(yi*8+xi, col);
		}
	}
}

inline INT8 vboy_state::get_bg_map_pixel(int num, int xpos, int ypos)
{
//  g_profiler.start(PROFILER_USER1);
	int x, y;
	UINT8 stepx, stepy;

	y = ypos >>3;
	x = xpos >>3;

	stepx = (x & 0x1c0) >> 6;
	stepy = ((y & 0x1c0) >> 6) * (stepx+1);
	UINT16 val = READ_BGMAP((x & 0x3f) + (64 * (y & 0x3f)) + ((num + stepx + stepy) * 0x1000));
	int pal = m_vip_regs.GPLT[(val >> 14) & 3];
	int code = val & 0x3fff;

	UINT16 data;
	UINT8 yi, xi, dat;

	yi = ypos & 7;
	data = READ_FONT(code * 8 + yi);
	xi = xpos & 7;
	dat = ((data >> (xi << 1)) & 0x03);

	if(dat == 0)
	{
		//g_profiler.stop();
		return -1;
	}
	//  g_profiler.stop();
	return (pal >> (dat*2)) & 3;
}

void vboy_state::draw_bg_map(bitmap_ind16 &bitmap, const rectangle &cliprect, UINT16 param_base, int mode, int gx, int gp, int gy, int mx, int mp, int my, int h, int w,
													UINT16 x_mask, UINT16 y_mask, UINT8 ovr, bool right, int bg_map_num)
{
//  g_profiler.start(PROFILER_USER2);
	int x,y;

	for(y=0;y<=h;y++)
	{
		INT32 y1 = (y+gy);

		if ((y1 < cliprect.min_y) || (y1 > cliprect.max_y))
			continue;

		int src_y = y+my;

		for(x=0;x<=w;x++)
		{
			INT32 x1 = (x+gx);

			x1 += right ? -gp : gp;

			if ((x1 < cliprect.min_x) || (x1 > cliprect.max_x))
				continue;

			int src_x;
			src_x = x+mx;
			if (mode==1)
				src_x += (INT16)READ_BGMAP(param_base + (y*2+(right ^ 1)));

			src_x += right ? -mp : mp;


			int pix = 0;
			if(ovr)
			{
				if ((src_x > x_mask || src_y > y_mask || src_x < 0 || src_y < 0))
				{
					g_profiler.start(PROFILER_USER3);
					pix = READ_OVR_TEMPDRAW_MAP((src_y & 7)*8+(src_x & 7));
					g_profiler.stop();
				}
				else
				{
					pix = get_bg_map_pixel(bg_map_num, src_x & x_mask, src_y & y_mask);
				}
			}
			else
			{
				pix = get_bg_map_pixel(bg_map_num, src_x & x_mask, src_y & y_mask);
			}

			if(pix != -1)
				bitmap.pix16(y1, x1) = m_palette->pen(pix & 3);
		}
	}
//  g_profiler.stop();
}

void vboy_state::draw_affine_map(bitmap_ind16 &bitmap, const rectangle &cliprect, UINT16 param_base, int gx, int gp, int gy, int h, int w,
														UINT16 x_mask, UINT16 y_mask, UINT8 ovr, bool right, int bg_map_num)
{
//  g_profiler.start(PROFILER_USER3);
	int x,y;

	for(y=0;y<=h;y++)
	{
		float h_skw = (INT16)READ_BGMAP(param_base + (y*8+0)) / 8.0;
		float prlx = (INT16)READ_BGMAP(param_base + (y*8+1)) / 8.0;
		float v_skw = (INT16)READ_BGMAP(param_base + (y*8+2)) / 8.0;
		float h_scl = (INT16)READ_BGMAP(param_base + (y*8+3)) / 512.0;
		float v_scl = (INT16)READ_BGMAP(param_base + (y*8+4)) / 512.0;

		h_skw += right ? -prlx : prlx;

		for(x=0;x<=w;x++)
		{
			INT32 src_x,src_y;
			INT16 y1 = (y+gy);
			INT16 x1 = (x+gx);
			int pix = 0;

			x1 += right ? -gp : gp;

			src_x = (INT32)((h_skw) + (h_scl * x));
			src_y = (INT32)((v_skw) + (v_scl * x));

			if(ovr && (src_y > y_mask || src_x > x_mask || src_x < 0 || src_y < 0))
			{
				pix = READ_OVR_TEMPDRAW_MAP((src_y & 7)*8+(src_x & 7));
			}
			else
			{
				pix = get_bg_map_pixel(bg_map_num, src_x & x_mask, src_y & y_mask);
			}

			if(pix != -1)
				if (cliprect.contains(x1, y1))
					bitmap.pix16(y1, x1) = m_palette->pen(pix & 3);
		}
	}
//  g_profiler.stop();
}

/*
x--- ---- ---- ---- [0] LON
-x-- ---- ---- ----     RON
--xx ---- ---- ----     BGM type
---- xx-- ---- ----     SCX
---- --xx ---- ----     SCY
---- ---- x--- ----     OVR
---- ---- -x-- ----     END
---- ---- --00 ----
---- ---- ---- xxxx     BGMAP_BASE
*/

UINT8 vboy_state::display_world(int num, bitmap_ind16 &bitmap, const rectangle &cliprect, bool right, int &cur_spt)
{
	num <<= 4;
	UINT16 def = READ_WORLD(num);
	UINT8 lon = (def >> 15) & 1;
	UINT8 ron = (def >> 14) & 1;
	UINT8 mode = (def >> 12) & 3;
	UINT16 scx = 64 << ((def >> 10) & 3);
	UINT16 scy = 64 << ((def >> 8) & 3);
	UINT8 ovr = (def >> 7) & 1;
	UINT8 end = (def >> 6) & 1;
	INT16 gx  = READ_WORLD(num+1);
	INT16 gp  = READ_WORLD(num+2);
	INT16 gy  = READ_WORLD(num+3);
	INT16 mx  = READ_WORLD(num+4);
	INT16 mp  = READ_WORLD(num+5);
	INT16 my  = READ_WORLD(num+6);
	UINT16 w  = READ_WORLD(num+7);
	UINT16 h  = READ_WORLD(num+8);
	UINT16 param_base = READ_WORLD(num+9) & 0xfff0;
	UINT16 ovr_char = READ_BGMAP(READ_WORLD(num+10));
	UINT8 bg_map_num = def & 0x0f;
	int i;

	if(end)
		return 1;

	if (mode < 2) // Normal / HBias Mode
	{
		if(ovr)
			fill_ovr_char(ovr_char & 0x3fff, m_vip_regs.GPLT[(ovr_char >> 14) & 3]);

		if (lon && (!right))
		{
			draw_bg_map(bitmap, cliprect, param_base, mode, gx, gp, gy, mx, mp, my, h,w, scx*8-1, scy*8-1, ovr, right, bg_map_num);
		}

		if (ron && (right))
		{
			draw_bg_map(bitmap, cliprect, param_base, mode, gx, gp, gy, mx, mp, my, h,w, scx*8-1, scy*8-1, ovr, right, bg_map_num);
		}
	}
	else if (mode==2) // Affine Mode
	{
		if(ovr)
			fill_ovr_char(ovr_char & 0x3fff, m_vip_regs.GPLT[(ovr_char >> 14) & 3]);

		if (lon && (!right))
		{
			draw_affine_map(bitmap, cliprect, param_base, gx, gp, gy, h,w, scx*8-1, scy*8-1, ovr, right, bg_map_num);
		}

		if (ron && (right))
		{
			draw_affine_map(bitmap, cliprect, param_base, gx, gp, gy, h,w, scx*8-1, scy*8-1, ovr, right, bg_map_num);
		}
	}
	else if (mode==3) // OBJ Mode
	{
		int start_offs, end_offs;

		if(cur_spt == -1)
		{
			popmessage("Cur spt used with -1 pointer!");
			return 0;
		}

		start_offs = m_vip_regs.SPT[cur_spt];

		end_offs = 0x3ff;
		if(cur_spt != 0)
			end_offs = m_vip_regs.SPT[cur_spt-1];

		i = start_offs;
		do
		{
			UINT16 start_ndx = i * 4;
			INT16 jx = READ_OBJECTS(start_ndx+0);
			INT16 jp = READ_OBJECTS(start_ndx+1) & 0x3fff;
			INT16 jy = READ_OBJECTS(start_ndx+2) & 0x1ff;
			UINT16 val = READ_OBJECTS(start_ndx+3);
			UINT8 jlon = (READ_OBJECTS(start_ndx+1) & 0x8000) >> 15;
			UINT8 jron = (READ_OBJECTS(start_ndx+1) & 0x4000) >> 14;

			if (!right && jlon)
				put_obj(bitmap, cliprect, (jx-jp) & 0x1ff, jy, val & 0x3fff, m_vip_regs.JPLT[(val>>14) & 3]);

			if(right && jron)
				put_obj(bitmap, cliprect, (jx+jp) & 0x1ff, jy, val & 0x3fff, m_vip_regs.JPLT[(val>>14) & 3]);

			i --;
			i &= 0x3ff;
		}while(i != end_offs);

		if((lon && !right) || (ron && right))
			cur_spt --;
	}

	return 0;
}

UINT32 vboy_state::screen_update_vboy_left(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
	bitmap.fill(m_palette->pen(m_vip_regs.BKCOL), cliprect);
	int cur_spt;

	if(!(m_vip_regs.DPCTRL & 2)) /* Don't bother if screen is off */
		return 0;

	cur_spt = 3;
	for(int i=31; i>=0; i--)
		if (display_world(i, bitmap, cliprect, false, cur_spt)) break;

	if(0)
	{
		int x,y;

		for(y=0;y<224;y++)
		{
			for(x=0;x<384;x++)
			{
				UINT8 pen;
				UINT8 pix;
				int yi;

				pen = m_l_frame_1[(x*0x40)+(y >> 2)];
				yi = ((y & 0x3)*2);
				pix = (pen >> yi) & 3;

				bitmap.pix16(y, x) = m_palette->pen(pix & 3);
			}
		}
	}

	return 0;
}

UINT32 vboy_state::screen_update_vboy_right(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
	bitmap.fill(m_palette->pen(m_vip_regs.BKCOL), cliprect);
	int cur_spt;

	if(!(m_vip_regs.DPCTRL & 2)) /* Don't bother if screen is off */
		return 0;

	cur_spt = 3;
	for(int i=31; i>=0; i--)
		if (display_world(i, bitmap, cliprect, true, cur_spt)) break;

	return 0;
}

/**********************************
 *
 * I/O
 *
 *********************************/

READ32_MEMBER( vboy_state::io_r )
{
	UINT32 value = 0x00;

	switch ((offset << 2))
	{
		case 0x10:  // KLB (Keypad Low Byte)
			value = m_vboy_regs.klb;    // 0x02 is always 1
			break;
		case 0x14:  // KHB (Keypad High Byte)
			value = m_vboy_regs.khb;
			break;
		case 0x18:  // TLB (Timer Low Byte)
			value = m_vboy_regs.tlb;
			break;
		case 0x1c:  // THB (Timer High Byte)
			value = m_vboy_regs.thb;
			break;
		case 0x20:  // TCR (Timer Control Reg)
			value = m_vboy_regs.tcr;
			break;
		case 0x24:  // WCR (Wait State Control Reg)
			value = m_vboy_regs.wcr;
			break;
		case 0x28:  // KCR (Keypad Control Reg)
			{
//              attotime new_time = machine().time();

//              if((new_time - m_input_latch_time) < m_maincpu->cycles_to_attotime(640))
					value |= machine().rand() & 2;

				value = m_vboy_regs.kcr | 0x4c;
			}
			break;
		case 0x00:  // LPC (Link Port Control Reg)
		case 0x04:  // LPC2 (Link Port Control Reg)
		case 0x08:  // LPT (Link Port Transmit)
		case 0x0c:  // LPR (Link Port Receive)
		default:
			logerror("Unemulated read: offset %08x\n", 0x02000000 + (offset << 2));
			break;
	}
	return value;
}

WRITE32_MEMBER( vboy_state::io_w )
{
	switch (offset<<2)
	{
		case 0x0c:  // LPR (Link Port Receive)
		case 0x10:  // KLB (Keypad Low Byte)
		case 0x14:  // KHB (Keypad High Byte)
			//logerror("Ilegal write: offset %02x should be only read\n", offset);
			break;
		case 0x18:  // TLB (Timer Low Byte)
			m_vboy_regs.tlb = data;
			m_vboy_timer.latch = m_vboy_regs.tlb | (m_vboy_timer.latch & 0xff00);
			break;
		case 0x1c:  // THB (Timer High Byte)
			m_vboy_regs.thb = data;
			m_vboy_timer.latch = (m_vboy_regs.thb<<8) | (m_vboy_timer.latch & 0xff);
			break;
		case 0x20:  // TCR (Timer Control Reg)
			/*
			    111- ---- always 1
			    ---x ---- timer select (1=20 us, 0=100 us)
			    ---- x--- timer irq
			    ---- -x-- resets timer zero flag
			    ---- --x- timer is zero flag
			    ---- ---x enables timer
			*/
			if (!(data & 0x08))
			{
				m_maincpu->set_input_line(1, CLEAR_LINE);
			}

			if (data & 1)
			{
				m_vboy_regs.tlb = m_vboy_timer.latch & 0xff;
				m_vboy_regs.thb = m_vboy_timer.latch >> 8;
				m_vboy_timer.count = m_vboy_timer.latch;

				// only start timer if tcr & 1 is 1 and wasn't before?
				if (!(m_vboy_regs.tcr & 1))
				{
					if (data & 0x10)
					{
						m_maintimer->adjust(attotime::from_hz(50000));
					}
					else
					{
						m_maintimer->adjust(attotime::from_hz(10000));
					}

				}
			}

			m_vboy_regs.tcr = (data & 0xfd) | (0xe4) | (m_vboy_regs.tcr & 2);   // according to docs: bits 5, 6 & 7 are unused and set to 1, bit 1 is read only.
			if(data & 4)
				m_vboy_regs.tcr &= 0xfd;
			break;
		case 0x24:  // WCR (Wait State Control Reg)
			m_vboy_regs.wcr = data | 0xfc;  // according to docs: bits 2 to 7 are unused and set to 1.
			break;
		case 0x28:  // KCR (Keypad Control Reg)
			if (data & 0x04 )
			{
				m_vboy_regs.klb = (ioport("INPUT")->read() & 0x00ff);
				m_vboy_regs.khb = (ioport("INPUT")->read() & 0xff00) >> 8;
				//m_input_latch_time = machine().time();
			}

			if (data & 1)
			{
				m_vboy_regs.klb = 0;
				m_vboy_regs.khb = 0;
				//m_input_latch_time = attotime::zero;
			}


			m_vboy_regs.kcr = (data | 0x48) & 0xfd; // according to docs: bit 6 & bit 3 are unused and set to 1, bit 1 is read only.
			break;
		case 0x00:  // LPC (Link Port Control Reg)
		case 0x04:  // LPC2 (Link Port Control Reg)
		case 0x08:  // LPT (Link Port Transmit)
		default:
			logerror("Unemulated write: offset %08x, data %04x\n", 0x02000000 + (offset << 2), data);
			break;
	}
}


/**********************************
 *
 * VIP
 *
 *********************************/
/*
TODO: brightness presumably isn't a linear algorythm, also REST needs to be taken into account (needs a working example)
*/
void vboy_state::m_set_brightness(void)
{
	int a,b,c;

	//d = (m_vip_regs.BRTA + m_vip_regs.BRTB + m_vip_regs.BRTC + m_vip_regs.REST);
	a = (0xff * (m_vip_regs.BRTA)) / 0x80;
	b = (0xff * (m_vip_regs.BRTA + m_vip_regs.BRTB)) / 0x80;
	c = (0xff * (m_vip_regs.BRTA + m_vip_regs.BRTB + m_vip_regs.BRTC)) / 0x80;

	if(a < 0) { a = 0; }
	if(b < 0) { b = 0; }
	if(c < 0) { c = 0; }
	if(a > 0xff) { a = 0xff; }
	if(b > 0xff) { b = 0xff; }
	if(c > 0xff) { c = 0xff; }

//  popmessage("%02x %02x %02x %02x",m_vip_regs.BRTA,m_vip_regs.BRTB,m_vip_regs.BRTC,m_vip_regs.REST);
	m_palette->set_pen_color(1, a,0,0);
	m_palette->set_pen_color(2, b,0,0);
	m_palette->set_pen_color(3, c,0,0);
}

READ16_MEMBER( vboy_state::vip_r )
{
	switch(offset << 1) {
		case 0x00:  //INTPND
					return m_vip_regs.INTPND;
		case 0x02:  //INTENB
					return m_vip_regs.INTENB;
		case 0x04:  //INTCLR
					logerror("Error reading INTCLR\n");
					break;
/*
        ---- -x-- ---- ---- LOCK (status column table address (CTA) lock)
        ---- --x- ---- ---- SYNCE (status of sync signal enable)
        ---- ---x ---- ---- RE (status of memory refresh cycle)
        ---- ---- x--- ---- FCLK
        ---- ---- -x-- ---- SCANRDY (active low)
        ---- ---- --xx xx-- DPBSY (current framebuffer displayed)
        ---- ---- --10 00-- RFB1
        ---- ---- --01 00-- LFB1
        ---- ---- --00 10-- RFB0
        ---- ---- --00 01-- LFB0
        ---- ---- ---- --x- DISP
*/
		case 0x20:  //DPSTTS
		{
			UINT16 res;

			res = m_vip_regs.DPCTRL & 0x0702;

			if(m_vip_regs.DPCTRL & 2)
			{
				if(m_row_num < 224/8)
				{
					if(m_displayfb == 0)
						res |= 0x0c;
					else
						res |= 0x30;
				}
			}

			res |= 0x40;
			//printf("%04x\n",res);
			return res;
		}
		case 0x22:  //DPCTRL
					return m_vip_regs.DPCTRL;
		case 0x24:  //BRTA
					return m_vip_regs.BRTA;
		case 0x26:  //BRTB
					return m_vip_regs.BRTB;
		case 0x28:  //BRTC
					return m_vip_regs.BRTC;
		case 0x2A:  //REST
					return m_vip_regs.REST;
		case 0x2E:  //FRMCYC
					return m_vip_regs.FRMCYC;
		case 0x30:  //CTA
					printf("Read CTA\n");
					return m_vip_regs.CTA;
		case 0x40:  //XPSTTS, piXel Processor STaTuS
		{
			/*
			x--- ---- ---- ---- SBOUT
			---x xxxx ---- ---- SBCOUNT
			---- ---- ---x ---- OVERTIME (process overflow)
			---- ---- ---- x--- XPBSY1 (second framebuffer busy flag)
			---- ---- ---- -x-- XPBSY0 (first framebfuffer busy flag)
			---- ---- ---- --x- XPEN (starts drawing at beginning of game frame)
			---- ---- ---- ---x XPRST (force drawing process to idle)
			*/
			UINT16 res;

			//printf("%d\n",row_num);

			res =  m_vip_regs.XPSTTS & 0x00f3; // empty ^^'
			res |= m_drawfb << 2;

			if(m_row_num < 224/8)
			{
				res |= 0x8000;
				res |= m_row_num<<8;
			}

			return res;
		}
		case 0x42:  //XPCTRL
					return m_vip_regs.XPCTRL;
		case 0x44:  //VER
					printf("%08x read VER\n",m_maincpu->pc());
					return m_vip_regs.VER;
		case 0x48:  //SPT0
					return m_vip_regs.SPT[0];
		case 0x4A:  //SPT1
					return m_vip_regs.SPT[1];
		case 0x4C:  //SPT2
					return m_vip_regs.SPT[2];
		case 0x4E:  //SPT3
					return m_vip_regs.SPT[3];
		case 0x60:  //GPLT0
					return m_vip_regs.GPLT[0];
		case 0x62:  //GPLT1
					return m_vip_regs.GPLT[1];
		case 0x64:  //GPLT2
					return m_vip_regs.GPLT[2];
		case 0x66:  //GPLT3
					return m_vip_regs.GPLT[3];
		case 0x68:  //JPLT0
					return m_vip_regs.JPLT[0];
		case 0x6A:  //JPLT1
					return m_vip_regs.JPLT[1];
		case 0x6C:  //JPLT2
					return m_vip_regs.JPLT[2];
		case 0x6E:  //JPLT3
					return m_vip_regs.JPLT[3];
		case 0x70:  //BKCOL
					return m_vip_regs.BKCOL;
		default:
					logerror("Unemulated read: addr %08x\n", offset * 2 + 0x0005f800);
					break;
	}
	return 0xffff;
}

WRITE16_MEMBER( vboy_state::vip_w )
{
	if(mem_mask != 0xffff)
		printf("%04x %02x\n",mem_mask,offset*2);

	switch(offset << 1) {
		/*
		    x--- ---- ---- ---- TIME_ERR
		    -x-- ---- ---- ---- XP_END
		    --x- ---- ---- ---- SB_HIT
		    ---- ---- ---x ---- FRAME_START
		    ---- ---- ---- x--- GAME_START
		    ---- ---- ---- -x-- RFB_END
		    ---- ---- ---- --x- LFB_END
		    ---- ---- ---- ---x SCAN_ERR
		*/
		case 0x00:  //INTPND
					logerror("Error writing INTPND\n");
					break;
		case 0x02:  //INTENB
					m_vip_regs.INTENB = data;
					m_set_irq(0);
					//printf("%04x ENB\n",data);
					break;
		case 0x04:  //INTCLR
					m_vip_regs.INTPND &= ~data;
					m_set_irq(0);
					//else
					//  printf("%04x\n",m_vip_regs.INTPND);
					break;
		case 0x20:  //DPSTTS
					logerror("Error writing DPSTTS\n");
					break;
/*
        ---- -x-- ---- ---- LOCK (status column table address (CTA) lock)
        ---- --x- ---- ---- SYNCE (status of sync signal enable)
        ---- ---x ---- ---- RE (status of memory refresh cycle)
        ---- ---- ---- --x- DISP
        ---- ---- ---- ---x DPRST (Resets the VIP internal counter)
*/
		case 0x22:  //DPCTRL
					m_vip_regs.DPCTRL = data & 0x0702;

					if(data & 1)
					{
						m_vip_regs.INTPND &= 0xe000; // reset FRAME_START, GAME_START, RFB_END, LFB_END and SCAN_ERR irqs
						m_set_irq(0);
					}
					break;
		case 0x24:  //BRTA
					m_vip_regs.BRTA = data;
					m_set_brightness();
					break;
		case 0x26:  //BRTB
					m_vip_regs.BRTB = data;
					m_set_brightness();
					break;
		case 0x28:  //BRTC
					m_vip_regs.BRTC = data;
					m_set_brightness();
					break;
		case 0x2A:  //REST
					m_vip_regs.REST = data;
					m_set_brightness();
					if(data)
						printf("%04x REST\n",data);
					break;
		case 0x2E:  //FRMCYC
					//printf("%d\n",data);
					m_vip_regs.FRMCYC = data;
					break;
		case 0x30:  //CTA
					m_vip_regs.CTA = data;
					printf("%04x CTA\n",data);
					break;
		case 0x40:  //XPSTTS
					logerror("Error writing XPSTTS\n");
					break;
		case 0x42:  //XPCTRL, w/o
					/*
					---- ---- ---- --x-
					---- ---- ---- ---x Reset Pixel Processor
					*/
					m_vip_regs.XPCTRL = data & 0x1f02;

					//if(data & 0x1f00)
					//  printf("%04x SBCMP\n",data);

					if(data & 1)
					{
						m_vip_regs.INTPND &= 0x1fff; // reset SB_HIT, XP_END and TIME_ERR irqs
						m_set_irq(0);
					}
					break;
		case 0x44:  //VER
					//m_vip_regs.VER = data;
					break;
		case 0x48:  //SPT0
					m_vip_regs.SPT[0] = data & 0x3ff;
					break;
		case 0x4A:  //SPT1
					m_vip_regs.SPT[1] = data & 0x3ff;
					break;
		case 0x4C:  //SPT2
					m_vip_regs.SPT[2] = data & 0x3ff;
					break;
		case 0x4E:  //SPT3
					m_vip_regs.SPT[3] = data & 0x3ff;
					break;
		case 0x60:  //GPLT0
					m_vip_regs.GPLT[0] = data;
					break;
		case 0x62:  //GPLT1
					m_vip_regs.GPLT[1] = data;
					break;
		case 0x64:  //GPLT2
					m_vip_regs.GPLT[2] = data;
					break;
		case 0x66:  //GPLT3
					m_vip_regs.GPLT[3] = data;
					break;
		case 0x68:  //JPLT0
					m_vip_regs.JPLT[0] = data & 0xfc;
					break;
		case 0x6A:  //JPLT1
					m_vip_regs.JPLT[1] = data & 0xfc;
					break;
		case 0x6C:  //JPLT2
					m_vip_regs.JPLT[2] = data & 0xfc;
					break;
		case 0x6E:  //JPLT3
					m_vip_regs.JPLT[3] = data & 0xfc;
					break;
		case 0x70:  //BKCOL
					m_vip_regs.BKCOL = data & 3;
					break;
		default:
					logerror("Unemulated write: addr %08x, data %04x\n", offset * 2 + 0x0005f800, data);
					break;
	}
}



WRITE16_MEMBER( vboy_state::vboy_font0_w )
{
	WRITE_FONT(offset);
}

WRITE16_MEMBER( vboy_state::vboy_font1_w )
{
	WRITE_FONT(offset+0x1000);
}

WRITE16_MEMBER( vboy_state::vboy_font2_w )
{
	WRITE_FONT(offset+0x2000);
}

WRITE16_MEMBER( vboy_state::vboy_font3_w )
{
	WRITE_FONT(offset+0x3000);
}

READ16_MEMBER( vboy_state::vboy_font0_r )
{
	return READ_FONT(offset);
}

READ16_MEMBER( vboy_state::vboy_font1_r )
{
	return READ_FONT(offset + 0x1000);
}

READ16_MEMBER( vboy_state::vboy_font2_r )
{
	return READ_FONT(offset + 0x2000);
}

READ16_MEMBER( vboy_state::vboy_font3_r )
{
	return READ_FONT(offset + 0x3000);
}

WRITE16_MEMBER( vboy_state::vboy_bgmap_w )
{
	m_bgmap[offset] = data | (m_bgmap[offset] & (mem_mask ^ 0xffff));
}

READ16_MEMBER( vboy_state::vboy_bgmap_r )
{
	return m_bgmap[offset];
}

READ8_MEMBER( vboy_state::vboy_lfb0_r ) { return m_l_frame_0[offset]; }
READ8_MEMBER( vboy_state::vboy_lfb1_r ) { return m_l_frame_1[offset]; }
READ8_MEMBER( vboy_state::vboy_rfb0_r ) { return m_r_frame_0[offset]; }
READ8_MEMBER( vboy_state::vboy_rfb1_r ) { return m_r_frame_1[offset]; }
WRITE8_MEMBER( vboy_state::vboy_lfb0_w ) { m_l_frame_0[offset] = data; }
WRITE8_MEMBER( vboy_state::vboy_lfb1_w ) { m_l_frame_1[offset] = data; }
WRITE8_MEMBER( vboy_state::vboy_rfb0_w ) { m_r_frame_0[offset] = data; }
WRITE8_MEMBER( vboy_state::vboy_rfb1_w ) { m_r_frame_1[offset] = data; }


static ADDRESS_MAP_START( vboy_mem, AS_PROGRAM, 32, vboy_state )
	ADDRESS_MAP_GLOBAL_MASK(0x07ffffff)
	AM_RANGE( 0x00000000, 0x00005fff ) AM_READWRITE8(vboy_lfb0_r,vboy_lfb0_w,0xffffffff) // L frame buffer 0
	AM_RANGE( 0x00006000, 0x00007fff ) AM_READWRITE16(vboy_font0_r, vboy_font0_w, 0xffffffff) // Font 0-511
	AM_RANGE( 0x00008000, 0x0000dfff ) AM_READWRITE8(vboy_lfb1_r,vboy_lfb1_w,0xffffffff) // L frame buffer 1
	AM_RANGE( 0x0000e000, 0x0000ffff ) AM_READWRITE16(vboy_font1_r, vboy_font1_w, 0xffffffff) // Font 512-1023
	AM_RANGE( 0x00010000, 0x00015fff ) AM_READWRITE8(vboy_rfb0_r,vboy_rfb0_w,0xffffffff)  // R frame buffer 0
	AM_RANGE( 0x00016000, 0x00017fff ) AM_READWRITE16(vboy_font2_r, vboy_font2_w, 0xffffffff) // Font 1024-1535
	AM_RANGE( 0x00018000, 0x0001dfff ) AM_READWRITE8(vboy_rfb1_r,vboy_rfb1_w,0xffffffff)  // R frame buffer 1
	AM_RANGE( 0x0001e000, 0x0001ffff ) AM_READWRITE16(vboy_font3_r, vboy_font3_w, 0xffffffff) // Font 1536-2047

	AM_RANGE( 0x00020000, 0x0003ffff ) AM_READWRITE16(vboy_bgmap_r,vboy_bgmap_w, 0xffffffff) // VIPC memory

	//AM_RANGE( 0x00040000, 0x0005ffff ) AM_RAM // VIPC
	AM_RANGE( 0x0005f800, 0x0005f87f ) AM_READWRITE16(vip_r, vip_w, 0xffffffff)

	AM_RANGE( 0x00078000, 0x00079fff ) AM_READWRITE16(vboy_font0_r, vboy_font0_w, 0xffffffff) // Font 0-511 mirror
	AM_RANGE( 0x0007a000, 0x0007bfff ) AM_READWRITE16(vboy_font1_r, vboy_font1_w, 0xffffffff) // Font 512-1023 mirror
	AM_RANGE( 0x0007c000, 0x0007dfff ) AM_READWRITE16(vboy_font2_r, vboy_font2_w, 0xffffffff) // Font 1024-1535 mirror
	AM_RANGE( 0x0007e000, 0x0007ffff ) AM_READWRITE16(vboy_font3_r, vboy_font3_w, 0xffffffff) // Font 1536-2047 mirror

	AM_RANGE( 0x01000000, 0x010005ff ) AM_DEVREADWRITE8("vbsnd", vboysnd_device, read, write, 0xffffffff)
	AM_RANGE( 0x02000000, 0x0200002b ) AM_MIRROR(0x0ffff00) AM_READWRITE(io_r, io_w) // Hardware control registers mask 0xff
	//AM_RANGE( 0x04000000, 0x04ffffff ) // Expansion area
	AM_RANGE( 0x05000000, 0x0500ffff ) AM_MIRROR(0x0ff0000) AM_RAM AM_SHARE("wram")// Main RAM - 64K mask 0xffff
//  AM_RANGE( 0x06000000, 0x06003fff ) AM_RAM AM_SHARE("nvram") // Cart RAM - 8K NVRAM
	AM_RANGE( 0x07000000, 0x071fffff ) AM_MIRROR(0x0e00000) AM_ROM AM_REGION("cartridge", 0) /* ROM */
ADDRESS_MAP_END

static ADDRESS_MAP_START( vboy_io, AS_IO, 32, vboy_state )
	ADDRESS_MAP_GLOBAL_MASK(0x07ffffff)
	AM_RANGE( 0x00000000, 0x00005fff ) AM_RAM AM_SHARE("l_frame_0") // L frame buffer 0
	AM_RANGE( 0x00006000, 0x00007fff ) AM_READWRITE16(vboy_font0_r, vboy_font0_w, 0xffffffff) // Font 0-511
	AM_RANGE( 0x00008000, 0x0000dfff ) AM_RAM AM_SHARE("l_frame_1") // L frame buffer 1
	AM_RANGE( 0x0000e000, 0x0000ffff ) AM_READWRITE16(vboy_font1_r, vboy_font1_w, 0xffffffff) // Font 512-1023
	AM_RANGE( 0x00010000, 0x00015fff ) AM_RAM AM_SHARE("r_frame_0") // R frame buffer 0
	AM_RANGE( 0x00016000, 0x00017fff ) AM_READWRITE16(vboy_font2_r, vboy_font2_w, 0xffffffff) // Font 1024-1535
	AM_RANGE( 0x00018000, 0x0001dfff ) AM_RAM AM_SHARE("r_frame_1") // R frame buffer 1
	AM_RANGE( 0x0001e000, 0x0001ffff ) AM_READWRITE16(vboy_font3_r, vboy_font3_w, 0xffffffff) // Font 1536-2047

	AM_RANGE( 0x00020000, 0x0003ffff ) AM_READWRITE16(vboy_bgmap_r,vboy_bgmap_w, 0xffffffff) // VIPC memory

	//AM_RANGE( 0x00040000, 0x0005ffff ) AM_RAM // VIPC
	AM_RANGE( 0x0005f800, 0x0005f87f ) AM_READWRITE16(vip_r, vip_w, 0xffffffff)

	AM_RANGE( 0x00078000, 0x00079fff ) AM_READWRITE16(vboy_font0_r, vboy_font0_w, 0xffffffff) // Font 0-511 mirror
	AM_RANGE( 0x0007a000, 0x0007bfff ) AM_READWRITE16(vboy_font1_r, vboy_font1_w, 0xffffffff) // Font 512-1023 mirror
	AM_RANGE( 0x0007c000, 0x0007dfff ) AM_READWRITE16(vboy_font2_r, vboy_font2_w, 0xffffffff) // Font 1024-1535 mirror
	AM_RANGE( 0x0007e000, 0x0007ffff ) AM_READWRITE16(vboy_font3_r, vboy_font3_w, 0xffffffff) // Font 1536-2047 mirror

	AM_RANGE( 0x01000000, 0x010005ff ) AM_DEVREADWRITE8("vbsnd", vboysnd_device, read, write, 0xffffffff)
	AM_RANGE( 0x02000000, 0x0200002b ) AM_MIRROR(0x0ffff00) AM_READWRITE(io_r, io_w) // Hardware control registers mask 0xff
//  AM_RANGE( 0x04000000, 0x04ffffff ) // Expansion area
	AM_RANGE( 0x05000000, 0x0500ffff ) AM_MIRROR(0x0ff0000) AM_RAM AM_SHARE("wram") // Main RAM - 64K mask 0xffff
	AM_RANGE( 0x06000000, 0x06003fff ) AM_RAM AM_SHARE("nvram") // Cart RAM - 8K NVRAM
	AM_RANGE( 0x07000000, 0x071fffff ) AM_MIRROR(0x0e00000) AM_ROM AM_REGION("cartridge", 0) /* ROM */
ADDRESS_MAP_END

/* Input ports */
static INPUT_PORTS_START( vboy )
	PORT_START("INPUT")
	PORT_BIT( 0x8000, IP_ACTIVE_HIGH, IPT_JOYSTICKRIGHT_DOWN ) PORT_PLAYER(1)
	PORT_BIT( 0x4000, IP_ACTIVE_HIGH, IPT_JOYSTICKRIGHT_LEFT ) PORT_PLAYER(1)
	PORT_BIT( 0x2000, IP_ACTIVE_HIGH, IPT_SELECT ) PORT_PLAYER(1)
	PORT_BIT( 0x1000, IP_ACTIVE_HIGH, IPT_START ) PORT_PLAYER(1)
	PORT_BIT( 0x0800, IP_ACTIVE_HIGH, IPT_JOYSTICKLEFT_UP ) PORT_PLAYER(1)
	PORT_BIT( 0x0400, IP_ACTIVE_HIGH, IPT_JOYSTICKLEFT_DOWN ) PORT_PLAYER(1)
	PORT_BIT( 0x0200, IP_ACTIVE_HIGH, IPT_JOYSTICKLEFT_LEFT ) PORT_PLAYER(1)
	PORT_BIT( 0x0100, IP_ACTIVE_HIGH, IPT_JOYSTICKLEFT_RIGHT ) PORT_PLAYER(1)
	PORT_BIT( 0x0080, IP_ACTIVE_HIGH, IPT_JOYSTICKRIGHT_RIGHT ) PORT_PLAYER(1)
	PORT_BIT( 0x0040, IP_ACTIVE_HIGH, IPT_JOYSTICKRIGHT_UP ) PORT_PLAYER(1)
	PORT_BIT( 0x0020, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_NAME("L") PORT_PLAYER(1) // Left button on back
	PORT_BIT( 0x0010, IP_ACTIVE_HIGH, IPT_BUTTON4 ) PORT_NAME("R") PORT_PLAYER(1) // Right button on back
	PORT_BIT( 0x0008, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_NAME("B") PORT_PLAYER(1) // B button (Mario Clash Jump button)
	PORT_BIT( 0x0004, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_NAME("A") PORT_PLAYER(1) // A button
	PORT_BIT( 0x0002, IP_ACTIVE_LOW,  IPT_UNUSED ) // Always 1
	PORT_BIT( 0x0001, IP_ACTIVE_HIGH, IPT_UNUSED ) // Battery low
INPUT_PORTS_END

void vboy_state::vboy_machine_stop()
{
	// only do this if the cart loader detected some form of backup
	if (m_nvptr != NULL)
	{
		device_image_interface *image = dynamic_cast<device_image_interface *>(m_nvimage);
		image->battery_save(m_nvptr, 0x10000);
	}
}

void vboy_state::machine_start()
{
	/* add a hook for battery save */
	machine().add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(vboy_state::vboy_machine_stop),this));

//  m_vboy_sram = auto_alloc_array(machine(), UINT32, 0x10000/4);
}

void vboy_state::machine_reset()
{
	/* Initial values taken from Reality Boy, to be verified when emulation improves */
	m_vboy_regs.lpc = 0x6d;
	m_vboy_regs.lpc2 = 0xff;
	m_vboy_regs.lpt = 0x00;
	m_vboy_regs.lpr = 0x00;
	m_vboy_regs.klb = 0x00;
	m_vboy_regs.khb = 0x00;
	m_vboy_regs.tlb = 0xff;
	m_vboy_regs.thb = 0xff;
	m_vboy_regs.tcr = 0xe4;
	m_vboy_regs.wcr = 0xfc;
	m_vboy_regs.kcr = 0x4c | 0x80;
	m_vip_regs.DPCTRL = 2; // ssquash relies on this at boot otherwise no frame_start irq is fired
	m_displayfb = 0;
	m_drawfb = 0;

	m_vboy_timer.count = 0;
	m_maintimer->adjust(attotime::never);
}


void vboy_state::m_timer_tick()
{
	if(m_vboy_timer.count > 0)
	{
		m_vboy_timer.count--;
		m_vboy_regs.tlb = m_vboy_timer.count & 0xff;
		m_vboy_regs.thb = m_vboy_timer.count >> 8;
	}

	if (m_vboy_timer.count == 0)
	{
		m_vboy_timer.count = m_vboy_timer.latch;
		m_vboy_regs.tcr |= 0x02;
		if(m_vboy_regs.tcr & 8)
		{
			m_maincpu->set_input_line(1, ASSERT_LINE);
		}
	}

	if (m_vboy_regs.tcr & 0x10)
	{
		m_maintimer->adjust(attotime::from_hz(50000));
	}
	else
	{
		m_maintimer->adjust(attotime::from_hz(10000));
	}
}

TIMER_DEVICE_CALLBACK_MEMBER(vboy_state::timer_main_tick)
{
	m_timer_tick();
}

TIMER_DEVICE_CALLBACK_MEMBER(vboy_state::timer_pad_tick)
{
	if((m_vboy_regs.kcr & 0x80) == 0)
		m_maincpu->set_input_line(0, HOLD_LINE);
}

PALETTE_INIT_MEMBER(vboy_state, vboy)
{
	palette.set_pen_color(0, rgb_t::black);
	palette.set_pen_color(1, rgb_t::black);
	palette.set_pen_color(2, rgb_t::black);
	palette.set_pen_color(3, rgb_t::black);
}

void vboy_state::m_set_irq(UINT16 irq_vector)
{
	m_vip_regs.INTPND |= irq_vector;

	if(m_vip_regs.INTENB & m_vip_regs.INTPND)
		m_maincpu->set_input_line(4, ASSERT_LINE);

	if((m_vip_regs.INTENB & m_vip_regs.INTPND) == 0)
		m_maincpu->set_input_line(4, CLEAR_LINE);
}

/* TODO: obviously all of this needs clean-ups and better implementation ... */
void vboy_state::m_scanline_tick(int scanline, UINT8 screen_type)
{
	//int frame_num = machine().first_screen()->frame_number();

	if(screen_type == 0)
		m_row_num = (scanline / 8) & 0x1f;

	if(scanline == 0)
	{
		if(m_vip_regs.DPCTRL & 2)
			m_set_irq(0x0010); // FRAME_START

		m_frame_count++;

		if(m_frame_count > m_vip_regs.FRMCYC)
		{
			m_set_irq(0x0008); // GAME_START
			m_frame_count = 0;
		}

		if(m_vip_regs.DPCTRL & 2)
			m_displayfb ^= 1;
	}

	if(scanline == 224)
	{
		if(m_displayfb)
			m_drawfb = 1;
		else
			m_drawfb = 2;
		m_set_irq(0x4000); // XPEND
	}

	if(scanline == 232)
	{
		m_drawfb = 0;
		m_set_irq(0x0002); // LFBEND
	}

	if(scanline == 240)
	{
		m_set_irq(0x0004); // RFBEND
	}

	if(m_row_num == ((m_vip_regs.XPCTRL & 0x1f00) >> 8))
	{
		m_set_irq(0x2000); // SBHIT
	}

}

TIMER_DEVICE_CALLBACK_MEMBER(vboy_state::vboy_scanlineL)
{
	int scanline = param;

	m_scanline_tick(scanline,0);
}

#if 0
TIMER_DEVICE_CALLBACK_MEMBER(vboy_state::vboy_scanlineR)
{
	int scanline = param;

	//m_scanline_tick(scanline,1);
}
#endif



READ32_MEMBER(vboy_state::sram_r)
{
	return m_vboy_sram[offset];
}

WRITE32_MEMBER(vboy_state::sram_w)
{
	COMBINE_DATA(&m_vboy_sram[offset]);
}


DEVICE_IMAGE_LOAD_MEMBER( vboy_state, vboy_cart )
{
	UINT32 chip = 0;
	UINT8 *ROM = memregion("cartridge")->base();
	UINT32 cart_size;

	m_nvptr = (UINT8 *)NULL;
	if (image.software_entry() == NULL)
	{
		cart_size = image.length();
		image.fread(ROM, cart_size);
		switch (cart_size)
		{
			case 0x001000:
				memcpy(ROM + 0x001000, ROM, 0x001000);
			case 0x002000:
				memcpy(ROM + 0x002000, ROM, 0x002000);
			case 0x004000:
				memcpy(ROM + 0x004000, ROM, 0x004000);
			case 0x008000:
				memcpy(ROM + 0x008000, ROM, 0x008000);
			case 0x010000:
				memcpy(ROM + 0x010000, ROM, 0x010000);
			case 0x020000:
				memcpy(ROM + 0x020000, ROM, 0x020000);
			case 0x040000:
				memcpy(ROM + 0x040000, ROM, 0x040000);
			case 0x080000:
				memcpy(ROM + 0x080000, ROM, 0x080000);
			case 0x100000:
				memcpy(ROM + 0x100000, ROM, 0x100000);
			default:
				break;
		}
	}
	else
	{
		cart_size = image.get_software_region_length("rom");
		memcpy(ROM, image.get_software_region("rom"), cart_size);

		UINT8 *tmp_eeprom = image.get_software_region("eeprom");
		if (tmp_eeprom)
			chip = 1;
	}

	if (chip)
	{
		m_nvptr = (UINT8 *)&m_vboy_sram;

		m_maincpu->space(AS_PROGRAM).install_read_handler(0x06000000, 0x0600ffff, read32_delegate(FUNC(vboy_state::sram_r),this));
		m_maincpu->space(AS_PROGRAM).install_write_handler(0x06000000, 0x0600ffff, write32_delegate(FUNC(vboy_state::sram_w),this));

		image.battery_load(m_nvptr, 0x10000, 0x00);
		m_nvimage = image;
	}
	else
	{
		m_nvimage = NULL;
	}

	return IMAGE_INIT_PASS;
}

static MACHINE_CONFIG_START( vboy, vboy_state )

	/* basic machine hardware */
	MCFG_CPU_ADD( "maincpu", V810, XTAL_20MHz )
	MCFG_CPU_PROGRAM_MAP(vboy_mem)
	MCFG_CPU_IO_MAP(vboy_io)
	MCFG_TIMER_DRIVER_ADD_SCANLINE("scantimer_l", vboy_state, vboy_scanlineL, "3dleft", 0, 1)
	//MCFG_TIMER_DRIVER_ADD_SCANLINE("scantimer_r", vboy_state, vboy_scanlineR, "3dright", 0, 1)


	// programmable timer
	MCFG_TIMER_DRIVER_ADD("timer_main", vboy_state, timer_main_tick)

	// pad ready, which should be once per VBL
	MCFG_TIMER_DRIVER_ADD_PERIODIC("timer_pad", vboy_state, timer_pad_tick, attotime::from_hz(50.038029f))

	/* video hardware */
	MCFG_DEFAULT_LAYOUT(layout_vboy)
	MCFG_PALETTE_ADD("palette", 4)
	MCFG_PALETTE_INIT_OWNER(vboy_state, vboy)

	/* Left screen */
	MCFG_SCREEN_ADD("3dleft", RASTER)
	MCFG_SCREEN_RAW_PARAMS(XTAL_20MHz/2,757,0,384,264,0,224)
	MCFG_SCREEN_UPDATE_DRIVER(vboy_state, screen_update_vboy_left)
	MCFG_SCREEN_PALETTE("palette")

	/* Right screen */
	MCFG_SCREEN_ADD("3dright", RASTER)
	MCFG_SCREEN_RAW_PARAMS(XTAL_20MHz/2,757,0,384,264,0,224)
	MCFG_SCREEN_UPDATE_DRIVER(vboy_state, screen_update_vboy_right)
	MCFG_SCREEN_PALETTE("palette")

	/* cartridge */
	MCFG_CARTSLOT_ADD("cart")
	MCFG_CARTSLOT_EXTENSION_LIST("vb,bin")
	MCFG_CARTSLOT_MANDATORY
	MCFG_CARTSLOT_INTERFACE("vboy_cart")
	MCFG_CARTSLOT_LOAD(vboy_state, vboy_cart)

	/* software lists */
	MCFG_SOFTWARE_LIST_ADD("cart_list","vboy")

	/* sound hardware */
	MCFG_SPEAKER_STANDARD_STEREO("lspeaker", "rspeaker")
	MCFG_VBOYSND_ADD("vbsnd")
	MCFG_SOUND_ROUTE(0, "lspeaker", 1.0)
	MCFG_SOUND_ROUTE(1, "rspeaker", 1.0)
MACHINE_CONFIG_END

/* ROM definition */
ROM_START( vboy )
	ROM_REGION( 0x2000000, "cartridge", ROMREGION_ERASEFF )
ROM_END

/* Driver */

/*    YEAR  NAME    PARENT  COMPAT   MACHINE    INPUT    INIT    COMPANY     FULLNAME       FLAGS */
CONS( 1995, vboy,   0,      0,       vboy,      vboy, driver_device,    0,    "Nintendo", "Virtual Boy", GAME_NOT_WORKING | GAME_IMPERFECT_SOUND)
