diff options
Diffstat (limited to 'src/mame/video/gb_lcd.c')
-rw-r--r-- | src/mame/video/gb_lcd.c | 2852 |
1 files changed, 2852 insertions, 0 deletions
diff --git a/src/mame/video/gb_lcd.c b/src/mame/video/gb_lcd.c new file mode 100644 index 00000000000..b46aece5a15 --- /dev/null +++ b/src/mame/video/gb_lcd.c @@ -0,0 +1,2852 @@ +// license:BSD-3-Clause +// copyright-holders:Wilbert Pol +/*************************************************************************** + + gb_lcd.c + + Video file to handle emulation of the Nintendo Game Boy. + + Original code Carsten Sorensen 1998 + Mess modifications, bug fixes and speedups Hans de Goede 1998 + Bug fixes, SGB and GBC code Anthony Kruize 2002 + Improvements to match real hardware Wilbert Pol 2006-2008 + + Timing is not accurate enough: + - Mode 3 takes 172 cycles (measuered with logic analyzer by costis) + +***************************************************************************/ + +#include "emu.h" +//#include "cpu/lr35902/lr35902.h" +#include "video/gb_lcd.h" + +/* Interrupts (copied from includes/gb.h)... */ +#define VBL_INT 0 /* V-Blank */ +#define LCD_INT 1 /* LCD Status */ +#define TIM_INT 2 /* Timer */ +#define SIO_INT 3 /* Serial I/O */ +#define EXT_INT 4 /* Joypad */ + + + +#define LCDCONT m_vid_regs[0x00] /* LCD control register */ +#define LCDSTAT m_vid_regs[0x01] /* LCD status register */ +#define SCROLLY m_vid_regs[0x02] /* Starting Y position of the background */ +#define SCROLLX m_vid_regs[0x03] /* Starting X position of the background */ +#define CURLINE m_vid_regs[0x04] /* Current screen line being scanned */ +#define CMPLINE m_vid_regs[0x05] /* Gen. int. when scan reaches this line */ +#define BGRDPAL m_vid_regs[0x07] /* Background palette */ +#define SPR0PAL m_vid_regs[0x08] /* Sprite palette #0 */ +#define SPR1PAL m_vid_regs[0x09] /* Sprite palette #1 */ +#define WNDPOSY m_vid_regs[0x0A] /* Window Y position */ +#define WNDPOSX m_vid_regs[0x0B] /* Window X position */ +#define KEY1 m_vid_regs[0x0D] /* Prepare speed switch */ +#define HDMA1 m_vid_regs[0x11] /* HDMA source high byte */ +#define HDMA2 m_vid_regs[0x12] /* HDMA source low byte */ +#define HDMA3 m_vid_regs[0x13] /* HDMA destination high byte */ +#define HDMA4 m_vid_regs[0x14] /* HDMA destination low byte */ +#define HDMA5 m_vid_regs[0x15] /* HDMA length/mode/start */ +#define GBCBCPS m_vid_regs[0x28] /* Backgound palette spec */ +#define GBCBCPD m_vid_regs[0x29] /* Backgound palette data */ +#define GBCOCPS m_vid_regs[0x2A] /* Object palette spec */ +#define GBCOCPD m_vid_regs[0x2B] /* Object palette data */ + +/* -- Super Game Boy specific -- */ +#define SGB_BORDER_PAL_OFFSET 64 /* Border colours stored from pal 4-7 */ +#define SGB_XOFFSET 48 /* GB screen starts at column 48 */ +#define SGB_YOFFSET 40 /* GB screen starts at row 40 */ + + +enum { + UNLOCKED=0, + LOCKED +}; + + +enum { + GB_LCD_STATE_LYXX_M3=1, + GB_LCD_STATE_LYXX_PRE_M0, + GB_LCD_STATE_LYXX_M0, + GB_LCD_STATE_LYXX_M0_SCX3, + GB_LCD_STATE_LYXX_M0_GBC_PAL, + GB_LCD_STATE_LYXX_M0_PRE_INC, + GB_LCD_STATE_LYXX_M0_INC, + GB_LCD_STATE_LY00_M2, + GB_LCD_STATE_LYXX_M2, + GB_LCD_STATE_LY9X_M1, + GB_LCD_STATE_LY9X_M1_INC, + GB_LCD_STATE_LY00_M1, + GB_LCD_STATE_LY00_M1_1, + GB_LCD_STATE_LY00_M1_2, + GB_LCD_STATE_LY00_M0 +}; + + +/* OAM contents on power up. + + The OAM area seems contain some kind of unit fingerprint. On each boot + the data is almost always the same. Some random bits are flipped between + different boots. It is currently unknown how much these fingerprints + differ between different units. + + OAM fingerprints taken from Wilbert Pol's own unit. + */ + +static const UINT8 dmg_oam_fingerprint[0x100] = { + 0xD8, 0xE6, 0xB3, 0x89, 0xEC, 0xDE, 0x11, 0x62, 0x0B, 0x7E, 0x48, 0x9E, 0xB9, 0x6E, 0x26, 0xC9, + 0x36, 0xF4, 0x7D, 0xE4, 0xD9, 0xCE, 0xFA, 0x5E, 0xA3, 0x77, 0x60, 0xFC, 0x1C, 0x64, 0x8B, 0xAC, + 0xB6, 0x74, 0x3F, 0x9A, 0x0E, 0xFE, 0xEA, 0xA9, 0x40, 0x3A, 0x7A, 0xB6, 0xF2, 0xED, 0xA8, 0x3E, + 0xAF, 0x2C, 0xD2, 0xF2, 0x01, 0xE0, 0x5B, 0x3A, 0x53, 0x6A, 0x1C, 0x6C, 0x20, 0xD9, 0x22, 0xB4, + 0x8C, 0x38, 0x71, 0x69, 0x3E, 0x93, 0xA3, 0x22, 0xCE, 0x76, 0x24, 0xE7, 0x1A, 0x14, 0x6B, 0xB1, + 0xF9, 0x3D, 0xBF, 0x3D, 0x74, 0x64, 0xCB, 0xF5, 0xDC, 0x9A, 0x53, 0xC6, 0x0E, 0x78, 0x34, 0xCB, + 0x42, 0xB3, 0xFF, 0x07, 0x73, 0xAE, 0x6C, 0xA2, 0x6F, 0x6A, 0xA4, 0x66, 0x0A, 0x8C, 0x40, 0xB3, + 0x9A, 0x3D, 0x39, 0x78, 0xAB, 0x29, 0xE7, 0xC5, 0x7A, 0xDD, 0x51, 0x95, 0x2B, 0xE4, 0x1B, 0xF6, + 0x31, 0x16, 0x34, 0xFE, 0x11, 0xF2, 0x5E, 0x11, 0xF3, 0x95, 0x66, 0xB9, 0x37, 0xC2, 0xAD, 0x6D, + 0x1D, 0xA7, 0x79, 0x06, 0xD7, 0xE5, 0x8F, 0xFA, 0x9C, 0x02, 0x0C, 0x31, 0x8B, 0x17, 0x2E, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const UINT8 mgb_oam_fingerprint[0x100] = { + 0xB9, 0xE9, 0x0D, 0x69, 0xBB, 0x7F, 0x00, 0x80, 0xE9, 0x7B, 0x79, 0xA2, 0xFD, 0xCF, 0xD8, 0x0A, + 0x87, 0xEF, 0x44, 0x11, 0xFE, 0x37, 0x10, 0x21, 0xFA, 0xFF, 0x00, 0x17, 0xF6, 0x4F, 0x83, 0x03, + 0x3A, 0xF4, 0x00, 0x24, 0xBB, 0xAE, 0x05, 0x01, 0xFF, 0xF7, 0x12, 0x48, 0xA7, 0x5E, 0xF6, 0x28, + 0x5B, 0xFF, 0x2E, 0x10, 0xFF, 0xB9, 0x50, 0xC8, 0xAF, 0x77, 0x2C, 0x1A, 0x62, 0xD7, 0x81, 0xC2, + 0xFD, 0x5F, 0xA0, 0x94, 0xAF, 0xFF, 0x51, 0x20, 0x36, 0x76, 0x50, 0x0A, 0xFD, 0xF6, 0x20, 0x00, + 0xFE, 0xF7, 0xA0, 0x68, 0xFF, 0xFC, 0x29, 0x51, 0xA3, 0xFA, 0x06, 0xC4, 0x94, 0xFF, 0x39, 0x0A, + 0xFF, 0x6C, 0x20, 0x20, 0xF1, 0xAD, 0x0C, 0x81, 0x56, 0xFB, 0x03, 0x82, 0xFF, 0xFF, 0x08, 0x58, + 0x96, 0x7E, 0x01, 0x4D, 0xFF, 0xE4, 0x82, 0xE3, 0x3D, 0xBB, 0x54, 0x00, 0x3D, 0xF3, 0x04, 0x21, + 0xB7, 0x39, 0xCC, 0x10, 0xF9, 0x5B, 0x80, 0x50, 0x3F, 0x6A, 0x1C, 0x21, 0x1F, 0xFA, 0xA8, 0x52, + 0x5F, 0xB3, 0x44, 0xA1, 0x96, 0x1E, 0x00, 0x27, 0x63, 0x77, 0x30, 0x54, 0x37, 0x6F, 0x60, 0x22, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const UINT8 cgb_oam_fingerprint[0x100] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x74, 0xFF, 0x09, 0x00, 0x9D, 0x61, 0xA8, 0x28, 0x36, 0x1E, 0x58, 0xAA, 0x75, 0x74, 0xA1, 0x42, + 0x05, 0x96, 0x40, 0x09, 0x41, 0x02, 0x60, 0x00, 0x1F, 0x11, 0x22, 0xBC, 0x31, 0x52, 0x22, 0x54, + 0x22, 0xA9, 0xC4, 0x00, 0x1D, 0xAD, 0x80, 0x0C, 0x5D, 0xFA, 0x51, 0x92, 0x93, 0x98, 0xA4, 0x04, + 0x22, 0xA9, 0xC4, 0x00, 0x1D, 0xAD, 0x80, 0x0C, 0x5D, 0xFA, 0x51, 0x92, 0x93, 0x98, 0xA4, 0x04, + 0x22, 0xA9, 0xC4, 0x00, 0x1D, 0xAD, 0x80, 0x0C, 0x5D, 0xFA, 0x51, 0x92, 0x93, 0x98, 0xA4, 0x04, + 0x22, 0xA9, 0xC4, 0x00, 0x1D, 0xAD, 0x80, 0x0C, 0x5D, 0xFA, 0x51, 0x92, 0x93, 0x98, 0xA4, 0x04 +}; + +/* + For an AGS in CGB mode this data is: */ +#if 0 +static const UINT8 abs_oam_fingerprint[0x100] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, + 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; +#endif + + +const device_type GB_LCD_DMG = &device_creator<gb_lcd_device>; +const device_type GB_LCD_MGB = &device_creator<mgb_lcd_device>; +const device_type GB_LCD_SGB = &device_creator<sgb_lcd_device>; +const device_type GB_LCD_CGB = &device_creator<cgb_lcd_device>; + + + +gb_lcd_device::gb_lcd_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source) + : device_t(mconfig, type, name, tag, owner, clock, shortname, source), + device_video_interface(mconfig, *this), + m_sgb_border_hack(0) +{ +} + +gb_lcd_device::gb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : device_t(mconfig, GB_LCD_DMG, "DMG LCD", tag, owner, clock, "dmg_lcd", __FILE__), + device_video_interface(mconfig, *this), + m_sgb_border_hack(0) +{ +} + +mgb_lcd_device::mgb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : gb_lcd_device(mconfig, GB_LCD_MGB, "MGB LCD", tag, owner, clock, "mgb_lcd", __FILE__) +{ +} + +sgb_lcd_device::sgb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : gb_lcd_device(mconfig, GB_LCD_SGB, "SGB LCD", tag, owner, clock, "sgb_lcd", __FILE__) +{ +} + +cgb_lcd_device::cgb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : gb_lcd_device(mconfig, GB_LCD_CGB, "CGB LCD", tag, owner, clock, "cgb_lcd", __FILE__) +{ +} + + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void gb_lcd_device::common_start() +{ + m_screen->register_screen_bitmap(m_bitmap); + save_item(NAME(m_bitmap)); + m_oam = auto_alloc_array_clear(machine(), UINT8, 0x100); + + machine().save().register_postload(save_prepost_delegate(FUNC(gb_lcd_device::videoptr_restore), this)); + + m_maincpu = machine().device<cpu_device>("maincpu"); + + save_pointer(NAME(m_oam), 0x100); + save_item(NAME(m_window_lines_drawn)); + save_item(NAME(m_vid_regs)); + save_item(NAME(m_bg_zbuf)); + + save_item(NAME(m_cgb_bpal)); + save_item(NAME(m_cgb_spal)); + + save_item(NAME(m_gb_bpal)); + save_item(NAME(m_gb_spal0)); + save_item(NAME(m_gb_spal1)); + + save_item(NAME(m_current_line)); + save_item(NAME(m_cmp_line)); + save_item(NAME(m_sprCount)); + save_item(NAME(m_sprite)); + save_item(NAME(m_previous_line)); + save_item(NAME(m_start_x)); + save_item(NAME(m_end_x)); + save_item(NAME(m_mode)); + save_item(NAME(m_state)); + save_item(NAME(m_lcd_irq_line)); + save_item(NAME(m_triggering_line_irq)); + save_item(NAME(m_line_irq)); + save_item(NAME(m_triggering_mode_irq)); + save_item(NAME(m_mode_irq)); + save_item(NAME(m_delayed_line_irq)); + save_item(NAME(m_sprite_cycles)); + save_item(NAME(m_scrollx_adjust)); + save_item(NAME(m_oam_locked)); + save_item(NAME(m_vram_locked)); + save_item(NAME(m_pal_locked)); + save_item(NAME(m_hdma_enabled)); + save_item(NAME(m_hdma_possible)); + save_item(NAME(m_gbc_mode)); + save_item(NAME(m_gb_tile_no_mod)); + save_item(NAME(m_vram_bank)); + + save_item(NAME(m_gb_chrgen_offs)); + save_item(NAME(m_gb_bgdtab_offs)); + save_item(NAME(m_gb_wndtab_offs)); + save_item(NAME(m_gbc_chrgen_offs)); + save_item(NAME(m_gbc_bgdtab_offs)); + save_item(NAME(m_gbc_wndtab_offs)); + + save_item(NAME(m_layer[0].enabled)); + save_item(NAME(m_layer[0].xindex)); + save_item(NAME(m_layer[0].xshift)); + save_item(NAME(m_layer[0].xstart)); + save_item(NAME(m_layer[0].xend)); + save_item(NAME(m_layer[0].bgline)); + save_item(NAME(m_layer[1].enabled)); + save_item(NAME(m_layer[1].xindex)); + save_item(NAME(m_layer[1].xshift)); + save_item(NAME(m_layer[1].xstart)); + save_item(NAME(m_layer[1].xend)); + save_item(NAME(m_layer[1].bgline)); +} + + +void gb_lcd_device::videoptr_restore() +{ + m_layer[0].bg_map = m_vram + m_gb_bgdtab_offs; + m_layer[0].bg_tiles = m_vram + m_gb_chrgen_offs; + m_layer[1].bg_map = m_vram + m_gb_wndtab_offs; + m_layer[1].bg_tiles = m_vram + m_gb_chrgen_offs; +} + +void cgb_lcd_device::videoptr_restore() +{ + m_layer[0].bg_map = m_vram + m_gb_bgdtab_offs; + m_layer[0].gbc_map = m_vram + m_gbc_bgdtab_offs; + m_layer[1].bg_map = m_vram + m_gb_wndtab_offs; + m_layer[1].gbc_map = m_vram + m_gbc_wndtab_offs; +} + + +void gb_lcd_device::device_start() +{ + common_start(); + m_lcd_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(gb_lcd_device::lcd_timer_proc),this)); + + m_vram = auto_alloc_array_clear(machine(), UINT8, 0x2000); + save_pointer(NAME(m_vram), 0x2000); + + memcpy(m_oam, dmg_oam_fingerprint, 0x100); +} + +void mgb_lcd_device::device_start() +{ + common_start(); + m_lcd_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(mgb_lcd_device::lcd_timer_proc),this)); + + m_vram = auto_alloc_array_clear(machine(), UINT8, 0x2000); + save_pointer(NAME(m_vram), 0x2000); + + memcpy(m_oam, mgb_oam_fingerprint, 0x100); +} + +void sgb_lcd_device::device_start() +{ + common_start(); + m_lcd_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sgb_lcd_device::lcd_timer_proc),this)); + + m_vram = auto_alloc_array_clear(machine(), UINT8, 0x2000); + save_pointer(NAME(m_vram), 0x2000); + + m_sgb_tile_data = auto_alloc_array_clear(machine(), UINT8, 0x2000); + save_pointer(NAME(m_sgb_tile_data), 0x2000); + + memset(m_sgb_tile_map, 0, sizeof(m_sgb_tile_map)); + + /* Some default colours for non-SGB games */ + m_sgb_pal[0] = 32767; + m_sgb_pal[1] = 21140; + m_sgb_pal[2] = 10570; + m_sgb_pal[3] = 0; + /* The rest of the colortable can be black */ + for (int i = 4; i < 8 * 16; i++) + m_sgb_pal[i] = 0; + + save_item(NAME(m_sgb_atf_data)); + save_item(NAME(m_sgb_atf)); + save_item(NAME(m_sgb_pal_data)); + save_item(NAME(m_sgb_pal_map)); + save_item(NAME(m_sgb_pal)); + save_item(NAME(m_sgb_tile_map)); + save_item(NAME(m_sgb_window_mask)); +} + +void cgb_lcd_device::device_start() +{ + common_start(); + m_lcd_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(cgb_lcd_device::lcd_timer_proc),this)); + + m_vram = auto_alloc_array_clear(machine(), UINT8, 0x4000); + save_pointer(NAME(m_vram), 0x4000); + + memcpy(m_oam, cgb_oam_fingerprint, 0x100); + + /* Background is initialised as white */ + for (int i = 0; i < 32; i++) + m_cgb_bpal[i] = 32767; + /* Sprites are supposed to be uninitialized, but we'll make them black */ + for (int i = 0; i < 32; i++) + m_cgb_spal[i] = 0; +} + + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void gb_lcd_device::common_reset() +{ + m_window_lines_drawn = 0; + + m_current_line = 0; + m_cmp_line = 0; + m_sprCount = 0; + m_previous_line = 0; + m_start_x = 0; + m_end_x = 0; + m_mode = 0; + m_state = 0; + m_lcd_irq_line = 0; + m_triggering_line_irq = 0; + m_line_irq = 0; + m_triggering_mode_irq = 0; + m_mode_irq = 0; + m_delayed_line_irq = 0; + m_sprite_cycles = 0; + m_scrollx_adjust = 0; + m_oam_locked = 0; + m_vram_locked = 0; + m_pal_locked = 0; + m_gbc_mode = 0; + m_gb_tile_no_mod = 0; + m_vram_bank = 0; + + m_gb_chrgen_offs = 0; + m_gb_bgdtab_offs = 0x1c00; + m_gb_wndtab_offs = 0x1c00; + + memset(&m_vid_regs, 0, sizeof(m_vid_regs)); + memset(&m_bg_zbuf, 0, sizeof(m_bg_zbuf)); + memset(&m_cgb_bpal, 0, sizeof(m_cgb_bpal)); + memset(&m_cgb_spal, 0, sizeof(m_cgb_spal)); + memset(&m_sprite, 0, sizeof(m_sprite)); + memset(&m_layer[0], 0, sizeof(m_layer[0])); + memset(&m_layer[1], 0, sizeof(m_layer[1])); + + // specific reg initialization + m_vid_regs[0x06] = 0xff; + + for (int i = 0x0c; i < _NR_GB_VID_REGS; i++) + m_vid_regs[i] = 0xff; + + LCDSTAT = 0x80; + LCDCONT = 0x00; /* Video hardware is turned off at boot time */ + m_current_line = CURLINE = CMPLINE = 0x00; + SCROLLX = SCROLLY = 0x00; + SPR0PAL = SPR1PAL = 0xFF; + WNDPOSX = WNDPOSY = 0x00; + + // Initialize palette arrays + for (int i = 0; i < 4; i++) + m_gb_bpal[i] = m_gb_spal0[i] = m_gb_spal1[i] = i; + +} + + +void gb_lcd_device::device_reset() +{ + common_reset(); + + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(456)); +} + +void sgb_lcd_device::device_reset() +{ + common_reset(); + + memset(m_sgb_tile_data, 0, 0x2000); + + m_sgb_window_mask = 0; + + memset(m_sgb_pal_map, 0, sizeof(m_sgb_pal_map)); + memset(m_sgb_atf_data, 0, sizeof(m_sgb_atf_data)); +} + +void cgb_lcd_device::device_reset() +{ + common_reset(); + + m_gbc_chrgen_offs = 0x2000; + m_gbc_bgdtab_offs = 0x3c00; + m_gbc_wndtab_offs = 0x3c00; + + /* HDMA disabled */ + m_hdma_enabled = 0; + m_hdma_possible = 0; + + m_gbc_mode = 1; +} + + + +inline void gb_lcd_device::plot_pixel(bitmap_ind16 &bitmap, int x, int y, UINT32 color) +{ + bitmap.pix16(y, x) = (UINT16)color; +} + +/* + Select which sprites should be drawn for the current scanline and return the + number of sprites selected. + */ +void gb_lcd_device::select_sprites() +{ + int /*yindex,*/ line, height; + UINT8 *oam = m_oam + 39 * 4; + + m_sprCount = 0; + + /* If video hardware is enabled and sprites are enabled */ + if ((LCDCONT & 0x80) && (LCDCONT & 0x02)) + { + /* Check for stretched sprites */ + if (LCDCONT & 0x04) + height = 16; + else + height = 8; + + //yindex = m_current_line; + line = m_current_line + 16; + + for (int i = 39; i >= 0; i--) + { + if (line >= oam[0] && line < (oam[0] + height) && oam[1] && oam[1] < 168) + { + /* We limit the sprite count to max 10 here; + proper games should not exceed this... */ + if (m_sprCount < 10) + { + m_sprite[m_sprCount] = i; + m_sprCount++; + } + } + oam -= 4; + } + } +} + +void gb_lcd_device::update_sprites() +{ + bitmap_ind16 &bitmap = m_bitmap; + UINT8 height, tilemask, line, *oam, *vram; + int yindex; + + if (LCDCONT & 0x04) + { + height = 16; + tilemask = 0xFE; + } + else + { + height = 8; + tilemask = 0xFF; + } + + yindex = m_current_line; + line = m_current_line + 16; + + oam = m_oam + 39 * 4; + vram = m_vram; + for (int i = 39; i >= 0; i--) + { + /* if sprite is on current line && x-coordinate && x-coordinate is < 168 */ + if (line >= oam[0] && line < (oam[0] + height) && oam[1] && oam[1] < 168) + { + UINT16 data; + UINT8 bit, *spal; + int xindex, adr; + + spal = (oam[3] & 0x10) ? m_gb_spal1 : m_gb_spal0; + xindex = oam[1] - 8; + if (oam[3] & 0x40) /* flip y ? */ + { + adr = (oam[2] & tilemask) * 16 + (height - 1 - line + oam[0]) * 2; + } + else + { + adr = (oam[2] & tilemask) * 16 + (line - oam[0]) * 2; + } + data = (vram[adr + 1] << 8) | vram[adr]; + + switch (oam[3] & 0xA0) + { + case 0xA0: /* priority is set (behind bgnd & wnd, flip x) */ + for (bit = 0; bit < 8; bit++, xindex++) + { + register int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) + plot_pixel(bitmap, xindex, yindex, spal[colour]); + data >>= 1; + } + break; + case 0x20: /* priority is not set (overlaps bgnd & wnd, flip x) */ + for (bit = 0; bit < 8; bit++, xindex++) + { + register int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if (colour && xindex >= 0 && xindex < 160) + plot_pixel(bitmap, xindex, yindex, spal[colour]); + data >>= 1; + } + break; + case 0x80: /* priority is set (behind bgnd & wnd, don't flip x) */ + for (bit = 0; bit < 8 && xindex < 160; bit++, xindex++) + { + register int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) + plot_pixel(bitmap, xindex, yindex, spal[colour]); + data <<= 1; + } + break; + case 0x00: /* priority is not set (overlaps bgnd & wnd, don't flip x) */ + for (bit = 0; bit < 8 && xindex < 160; bit++, xindex++) + { + register int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + if (colour && xindex >= 0 && xindex < 160) + plot_pixel(bitmap, xindex, yindex, spal[colour]); + data <<= 1; + } + break; + } + } + oam -= 4; + } +} + +void gb_lcd_device::update_scanline() +{ + bitmap_ind16 &bitmap = m_bitmap; + + g_profiler.start(PROFILER_VIDEO); + + /* Make sure we're in mode 3 */ + if ((LCDSTAT & 0x03) == 0x03) + { + /* Calculate number of pixels to render based on time still left on the timer */ + UINT32 cycles_to_go = m_maincpu->attotime_to_cycles(m_lcd_timer->remaining()); + int l = 0; + + if (m_start_x < 0) + { + /* Window is enabled if the hardware says so AND the current scanline is + * within the window AND the window X coordinate is <=166 */ + m_layer[1].enabled = ((LCDCONT & 0x20) && (m_current_line >= WNDPOSY) && (WNDPOSX <= 166)) ? 1 : 0; + + /* BG is enabled if the hardware says so AND (window_off OR (window_on + * AND window's X position is >=7)) */ + m_layer[0].enabled = ((LCDCONT & 0x01) && ((!m_layer[1].enabled) || (m_layer[1].enabled && (WNDPOSX >= 7)))) ? 1 : 0; + + if (m_layer[0].enabled) + { + m_layer[0].bgline = (SCROLLY + m_current_line) & 0xFF; + m_layer[0].bg_map = m_vram + m_gb_bgdtab_offs; + m_layer[0].bg_tiles = m_vram + m_gb_chrgen_offs; + m_layer[0].xindex = SCROLLX >> 3; + m_layer[0].xshift = SCROLLX & 7; + m_layer[0].xstart = 0; + m_layer[0].xend = 160; + } + + if (m_layer[1].enabled) + { + int xpos = WNDPOSX - 7; /* Window is offset by 7 pixels */ + if (xpos < 0) + xpos = 0; + + m_layer[1].bgline = m_window_lines_drawn; + m_layer[1].bg_map = m_vram + m_gb_wndtab_offs; + m_layer[1].bg_tiles = m_vram + m_gb_chrgen_offs; + m_layer[1].xindex = 0; + m_layer[1].xshift = 0; + m_layer[1].xstart = xpos; + m_layer[1].xend = 160; + m_layer[0].xend = xpos; + } + m_start_x = 0; + } + + if (cycles_to_go < 160) + { + m_end_x = MIN(160 - cycles_to_go, 160); + /* Draw empty pixels when the background is disabled */ + if (!(LCDCONT & 0x01)) + { + rectangle r(m_start_x, m_end_x - 1, m_current_line, m_current_line); + bitmap.fill(m_gb_bpal[0], r); + } + while (l < 2) + { + UINT8 xindex, *map, *tiles; + UINT16 data; + int i, tile_index; + + if (!m_layer[l].enabled) + { + l++; + continue; + } + map = m_layer[l].bg_map + ((m_layer[l].bgline << 2) & 0x3E0); + tiles = m_layer[l].bg_tiles + ((m_layer[l].bgline & 7) << 1); + xindex = m_start_x; + if (xindex < m_layer[l].xstart) + xindex = m_layer[l].xstart; + i = m_end_x; + if (i > m_layer[l].xend) + i = m_layer[l].xend; + i = i - xindex; + + tile_index = (map[m_layer[l].xindex] ^ m_gb_tile_no_mod) * 16; + data = tiles[tile_index] | (tiles[tile_index+1] << 8); + data <<= m_layer[l].xshift; + + while (i > 0) + { + while ((m_layer[l].xshift < 8) && i) + { + register int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + plot_pixel(bitmap, xindex, m_current_line, m_gb_bpal[colour]); + m_bg_zbuf[xindex] = colour; + xindex++; + data <<= 1; + m_layer[l].xshift++; + i--; + } + if (m_layer[l].xshift == 8) + { + /* Take possible changes to SCROLLY into account */ + if (l == 0) + { + m_layer[0].bgline = (SCROLLY + m_current_line) & 0xFF; + map = m_layer[l].bg_map + ((m_layer[l].bgline << 2) & 0x3E0); + tiles = m_layer[l].bg_tiles + ((m_layer[l].bgline & 7) << 1); + } + + m_layer[l].xindex = (m_layer[l].xindex + 1) & 31; + m_layer[l].xshift = 0; + tile_index = (map[m_layer[l].xindex] ^ m_gb_tile_no_mod) * 16; + data = tiles[tile_index] | (tiles[tile_index + 1] << 8); + } + } + l++; + } + if (m_end_x == 160 && LCDCONT & 0x02) + { + update_sprites(); + } + m_start_x = m_end_x; + } + } + else + { + if (!(LCDCONT & 0x80)) + { + /* Draw an empty line when LCD is disabled */ + if (m_previous_line != m_current_line) + { + if (m_current_line < 144) + { + const rectangle &r = m_screen->visible_area(); + rectangle r1(r.min_x, r.max_x, m_current_line, m_current_line); + bitmap.fill(0, r1); + } + m_previous_line = m_current_line; + } + } + } + + g_profiler.stop(); +} + +/* --- Super Game Boy Specific --- */ + +void sgb_lcd_device::update_sprites() +{ + bitmap_ind16 &bitmap = m_bitmap; + UINT8 height, tilemask, line, *oam, *vram, pal; + INT16 yindex; + + if (LCDCONT & 0x04) + { + height = 16; + tilemask = 0xFE; + } + else + { + height = 8; + tilemask = 0xFF; + } + + /* Offset to center of screen */ + yindex = m_current_line + SGB_YOFFSET; + line = m_current_line + 16; + + oam = m_oam + 39 * 4; + vram = m_vram; + for (int i = 39; i >= 0; i--) + { + /* if sprite is on current line && x-coordinate && x-coordinate is < 168 */ + if (line >= oam[0] && line < (oam[0] + height) && oam[1] && oam[1] < 168) + { + UINT16 data; + UINT8 bit, *spal; + INT16 xindex; + int adr; + + spal = (oam[3] & 0x10) ? m_gb_spal1 : m_gb_spal0; + xindex = oam[1] - 8; + if (oam[3] & 0x40) /* flip y ? */ + { + adr = (oam[2] & tilemask) * 16 + (height - 1 - line + oam[0]) * 2; + } + else + { + adr = (oam[2] & tilemask) * 16 + (line - oam[0]) * 2; + } + data = (vram[adr + 1] << 8) | vram[adr]; + + /* Find the palette to use */ + // If sprite started before the start of the line we may need to pick a different pal_map entry? + pal = m_sgb_pal_map[(xindex < 0) ? 0 : (xindex >> 3)][((yindex - SGB_YOFFSET) >> 3)] << 2; + + /* Offset to center of screen */ + xindex += SGB_XOFFSET; + + switch (oam[3] & 0xA0) + { + case 0xA0: /* priority is set (behind bgnd & wnd, flip x) */ + for (bit = 0; bit < 8; bit++, xindex++) + { + register int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour && !m_bg_zbuf[xindex - SGB_XOFFSET]) + plot_pixel(bitmap, xindex, yindex, m_sgb_pal[pal + spal[colour]]); + data >>= 1; + } + break; + case 0x20: /* priority is not set (overlaps bgnd & wnd, flip x) */ + for (bit = 0; bit < 8; bit++, xindex++) + { + register int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour) + plot_pixel(bitmap, xindex, yindex, m_sgb_pal[pal + spal[colour]]); + data >>= 1; + } + break; + case 0x80: /* priority is set (behind bgnd & wnd, don't flip x) */ + for (bit = 0; bit < 8; bit++, xindex++) + { + register int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour && !m_bg_zbuf[xindex - SGB_XOFFSET]) + plot_pixel(bitmap, xindex, yindex, m_sgb_pal[pal + spal[colour]]); + data <<= 1; + } + break; + case 0x00: /* priority is not set (overlaps bgnd & wnd, don't flip x) */ + for (bit = 0; bit < 8; bit++, xindex++) + { + register int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour) + plot_pixel(bitmap, xindex, yindex, m_sgb_pal[pal + spal[colour]]); + data <<= 1; + } + break; + } + } + oam -= 4; + } +} + + +void sgb_lcd_device::refresh_border() +{ + UINT16 data, data2; + UINT8 *tiles, *tiles2; + + for (UINT16 yidx = 0; yidx < 224; yidx++) + { + UINT8 *map = m_sgb_tile_map + ((yidx >> 3) * 64); + UINT16 xindex = 0; + + for (UINT16 xidx = 0; xidx < 64; xidx += 2) + { + if (map[xidx + 1] & 0x80) /* Vertical flip */ + tiles = m_sgb_tile_data + ((7 - (yidx % 8)) << 1); + else /* No vertical flip */ + tiles = m_sgb_tile_data + ((yidx % 8) << 1); + tiles2 = tiles + 16; + + UINT8 pal = (map[xidx + 1] & 0x1C) >> 2; + if (pal == 0) + pal = 1; + pal <<= 4; + + if (m_sgb_border_hack) + { /* A few games do weird stuff */ + UINT8 tileno = map[xidx]; + if (tileno >= 128) tileno = ((64 + tileno) % 128) + 128; + else tileno = (64 + tileno) % 128; + data = tiles[tileno * 32] | (tiles[(tileno * 32) + 1] << 8); + data2 = tiles2[tileno * 32] | (tiles2[(tileno * 32) + 1] << 8); + } + else + { + data = tiles[map[xidx] * 32] | (tiles[(map[xidx] * 32) + 1] << 8); + data2 = tiles2[map[xidx] * 32] | (tiles2[(map[xidx] * 32) + 1] << 8); + } + + for (int i = 0; i < 8; i++) + { + register UINT8 colour; + if ((map[xidx + 1] & 0x40)) /* Horizontal flip */ + { + colour = ((data & 0x0001) ? 1 : 0) | ((data & 0x0100) ? 2 : 0) | + ((data2 & 0x0001) ? 4 : 0) | ((data2 & 0x0100) ? 8 : 0); + data >>= 1; + data2 >>= 1; + } + else /* No horizontal flip */ + { + colour = ((data & 0x0080) ? 1 : 0) | ((data & 0x8000) ? 2 : 0) | + ((data2 & 0x0080) ? 4 : 0) | ((data2 & 0x8000) ? 8 : 0); + data <<= 1; + data2 <<= 1; + } + /* A slight hack below so we don't draw over the GB screen. + * Drawing there is allowed, but due to the way we draw the + * scanline, it can obscure the screen even when it shouldn't. + */ + if (!((yidx >= SGB_YOFFSET && yidx < SGB_YOFFSET + 144) && + (xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160))) + { + plot_pixel(m_bitmap, xindex, yidx, m_sgb_pal[pal + colour]); + } + xindex++; + } + } + } +} + +void sgb_lcd_device::update_scanline() +{ + bitmap_ind16 &bitmap = m_bitmap; + + g_profiler.start(PROFILER_VIDEO); + + if ((LCDSTAT & 0x03) == 0x03) + { + /* Calcuate number of pixels to render based on time still left on the timer */ + UINT32 cycles_to_go = m_maincpu->attotime_to_cycles(m_lcd_timer->remaining()); + int l = 0; + + if (m_start_x < 0) + { + /* Window is enabled if the hardware says so AND the current scanline is + * within the window AND the window X coordinate is <=166 */ + m_layer[1].enabled = ((LCDCONT & 0x20) && m_current_line >= WNDPOSY && WNDPOSX <= 166) ? 1 : 0; + + /* BG is enabled if the hardware says so AND (window_off OR (window_on + * AND window's X position is >=7 )) */ + m_layer[0].enabled = ((LCDCONT & 0x01) && ((!m_layer[1].enabled) || (m_layer[1].enabled && WNDPOSX >= 7))) ? 1 : 0; + + if (m_layer[0].enabled) + { + m_layer[0].bgline = (SCROLLY + m_current_line) & 0xFF; + m_layer[0].bg_map = m_vram + m_gb_bgdtab_offs; + m_layer[0].bg_tiles = m_vram + m_gb_chrgen_offs; + m_layer[0].xindex = SCROLLX >> 3; + m_layer[0].xshift = SCROLLX & 7; + m_layer[0].xstart = 0; + m_layer[0].xend = 160; + } + + if (m_layer[1].enabled) + { + int xpos; + + /* Window X position is offset by 7 so we'll need to adjust */ + xpos = WNDPOSX - 7; + if (xpos < 0) + xpos = 0; + + m_layer[1].bgline = m_window_lines_drawn; + m_layer[1].bg_map = m_vram + m_gb_wndtab_offs; + m_layer[1].bg_tiles = m_vram + m_gb_chrgen_offs; + m_layer[1].xindex = 0; + m_layer[1].xshift = 0; + m_layer[1].xstart = xpos; + m_layer[1].xend = 160; + m_layer[0].xend = xpos; + } + m_start_x = 0; + } + + if (cycles_to_go == 0) + { + /* Does this belong here? or should it be moved to the else block */ + /* Handle SGB mask */ + switch (m_sgb_window_mask) + { + case 1: /* Freeze screen */ + return; + case 2: /* Blank screen (black) */ + { + rectangle r(SGB_XOFFSET, SGB_XOFFSET + 160-1, SGB_YOFFSET, SGB_YOFFSET + 144 - 1); + bitmap.fill(0, r); + } + return; + case 3: /* Blank screen (white - or should it be color 0?) */ + { + rectangle r(SGB_XOFFSET, SGB_XOFFSET + 160 - 1, SGB_YOFFSET, SGB_YOFFSET + 144 - 1); + bitmap.fill(32767, r); + } + return; + } + + /* Draw the "border" if we're on the first line */ + if (m_current_line == 0) + { + refresh_border(); + } + } + if (cycles_to_go < 160) + { + m_end_x = MIN(160 - cycles_to_go,160); + + /* if background or screen disabled clear line */ + if (!(LCDCONT & 0x01)) + { + rectangle r(SGB_XOFFSET, SGB_XOFFSET + 160 - 1, m_current_line + SGB_YOFFSET, m_current_line + SGB_YOFFSET); + bitmap.fill(0, r); + } + while (l < 2) + { + UINT8 xindex, sgb_palette, *map, *tiles; + UINT16 data; + int i, tile_index; + + if (!m_layer[l].enabled) + { + l++; + continue; + } + map = m_layer[l].bg_map + ((m_layer[l].bgline << 2) & 0x3E0); + tiles = m_layer[l].bg_tiles + ((m_layer[l].bgline & 7) << 1); + xindex = m_start_x; + if (xindex < m_layer[l].xstart) + xindex = m_layer[l].xstart; + i = m_end_x; + if (i > m_layer[l].xend) + i = m_layer[l].xend; + i = i - xindex; + + tile_index = (map[m_layer[l].xindex] ^ m_gb_tile_no_mod) * 16; + data = tiles[tile_index] | (tiles[tile_index + 1] << 8); + data <<= m_layer[l].xshift; + + while (i > 0) + { + /* Figure out which palette we're using */ + assert(((m_end_x - i) >> 3) >= 0 && ((m_end_x - i) >> 3) < ARRAY_LENGTH(m_sgb_pal_map)); + sgb_palette = m_sgb_pal_map[(m_end_x - i) >> 3][m_current_line >> 3] << 2; + + while ((m_layer[l].xshift < 8) && i) + { + register int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + plot_pixel(bitmap, xindex + SGB_XOFFSET, m_current_line + SGB_YOFFSET, m_sgb_pal[sgb_palette + m_gb_bpal[colour]]); + m_bg_zbuf[xindex] = colour; + xindex++; + data <<= 1; + m_layer[l].xshift++; + i--; + } + if (m_layer[l].xshift == 8) + { + /* Take possible changes to SCROLLY into account */ + if (l == 0) + { + m_layer[0].bgline = (SCROLLY + m_current_line) & 0xFF; + map = m_layer[l].bg_map + ((m_layer[l].bgline << 2) & 0x3E0); + tiles = m_layer[l].bg_tiles + ((m_layer[l].bgline & 7) << 1); + } + + m_layer[l].xindex = (m_layer[l].xindex + 1) & 31; + m_layer[l].xshift = 0; + tile_index = (map[m_layer[l].xindex] ^ m_gb_tile_no_mod) * 16; + data = tiles[tile_index] | (tiles[tile_index + 1] << 8); + } + } + l++; + } + if ((m_end_x == 160) && (LCDCONT & 0x02)) + { + update_sprites(); + } + m_start_x = m_end_x; + } + } + else + { + if (!(LCDCONT * 0x80)) + { + /* if screen disabled clear line */ + if (m_previous_line != m_current_line) + { + /* Also refresh border here??? */ + if (m_current_line < 144) + { + rectangle r(SGB_XOFFSET, SGB_XOFFSET + 160 - 1, m_current_line + SGB_YOFFSET, m_current_line + SGB_YOFFSET); + bitmap.fill(0, r); + } + m_previous_line = m_current_line; + } + } + } + + g_profiler.stop(); +} + +/* --- Game Boy Color Specific --- */ + +void cgb_lcd_device::update_sprites() +{ + bitmap_ind16 &bitmap = m_bitmap; + UINT8 height, tilemask, line, *oam; + int xindex, yindex; + + if (LCDCONT & 0x04) + { + height = 16; + tilemask = 0xFE; + } + else + { + height = 8; + tilemask = 0xFF; + } + + yindex = m_current_line; + line = m_current_line + 16; + + oam = m_oam + 39 * 4; + for (int i = 39; i >= 0; i--) + { + /* if sprite is on current line && x-coordinate && x-coordinate is < 168 */ + if (line >= oam[0] && line < (oam[0] + height) && oam[1] && oam[1] < 168) + { + UINT16 data; + UINT8 bit, pal; + + /* Handle mono mode for GB games */ + if (!m_gbc_mode) + pal = (oam[3] & 0x10) ? 4 : 0; + else + pal = ((oam[3] & 0x7) * 4); + + xindex = oam[1] - 8; + if (oam[3] & 0x40) /* flip y ? */ + { + data = *((UINT16 *) &m_vram[((oam[3] & 0x8)<<10) + (oam[2] & tilemask) * 16 + (height - 1 - line + oam[0]) * 2]); + } + else + { + data = *((UINT16 *) &m_vram[((oam[3] & 0x8)<<10) + (oam[2] & tilemask) * 16 + (line - oam[0]) * 2]); + } + + data = LITTLE_ENDIANIZE_INT16(data); + + switch (oam[3] & 0xA0) + { + case 0xA0: /* priority is set (behind bgnd & wnd, flip x) */ + for (bit = 0; bit < 8; bit++, xindex++) + { + register int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) + { + if (!m_gbc_mode) + colour = pal ? m_gb_spal1[colour] : m_gb_spal0[colour]; + plot_pixel(bitmap, xindex, yindex, m_cgb_spal[pal + colour]); + } + data >>= 1; + } + break; + case 0x20: /* priority is not set (overlaps bgnd & wnd, flip x) */ + for (bit = 0; bit < 8; bit++, xindex++) + { + register int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if ((m_bg_zbuf[xindex] & 0x80) && (m_bg_zbuf[xindex] & 0x7f) && (LCDCONT & 0x1)) + colour = 0; + if (colour && xindex >= 0 && xindex < 160) + { + if (!m_gbc_mode) + colour = pal ? m_gb_spal1[colour] : m_gb_spal0[colour]; + plot_pixel(bitmap, xindex, yindex, m_cgb_spal[pal + colour]); + } + data >>= 1; + } + break; + case 0x80: /* priority is set (behind bgnd & wnd, don't flip x) */ + for (bit = 0; bit < 8; bit++, xindex++) + { + register int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) + { + if (!m_gbc_mode) + colour = pal ? m_gb_spal1[colour] : m_gb_spal0[colour]; + plot_pixel(bitmap, xindex, yindex, m_cgb_spal[pal + colour]); + } + data <<= 1; + } + break; + case 0x00: /* priority is not set (overlaps bgnd & wnd, don't flip x) */ + for (bit = 0; bit < 8; bit++, xindex++) + { + if (xindex >= 0 && xindex < 160) + { + register int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + if ((m_bg_zbuf[xindex] & 0x80) && (m_bg_zbuf[xindex] & 0x7f) && (LCDCONT & 0x1)) + colour = 0; + if (colour) + { + if (!m_gbc_mode) + colour = pal ? m_gb_spal1[colour] : m_gb_spal0[colour]; + plot_pixel(bitmap, xindex, yindex, m_cgb_spal[pal + colour]); + } + } + data <<= 1; + } + break; + } + } + oam -= 4; + } +} + +void cgb_lcd_device::update_scanline() +{ + bitmap_ind16 &bitmap = m_bitmap; + + g_profiler.start(PROFILER_VIDEO); + + if ((LCDSTAT & 0x03) == 0x03) + { + /* Calcuate number of pixels to render based on time still left on the timer */ + UINT32 cycles_to_go = m_maincpu->attotime_to_cycles(m_lcd_timer->remaining()); + int l = 0; + + if (m_start_x < 0) + { + /* Window is enabled if the hardware says so AND the current scanline is + * within the window AND the window X coordinate is <=166 */ + m_layer[1].enabled = ((LCDCONT & 0x20) && (m_current_line >= WNDPOSY) && (WNDPOSX <= 166)) ? 1 : 0; + + /* BG is enabled if the hardware says so AND (window_off OR (window_on + * AND window's X position is >=7 )) */ + m_layer[0].enabled = ((LCDCONT & 0x01) && ((!m_layer[1].enabled) || (m_layer[1].enabled && (WNDPOSX >= 7)))) ? 1 : 0; + + if (m_layer[0].enabled) + { + m_layer[0].bgline = (SCROLLY + m_current_line) & 0xFF; + m_layer[0].bg_map = m_vram + m_gb_bgdtab_offs; + m_layer[0].gbc_map = m_vram + m_gbc_bgdtab_offs; + m_layer[0].xindex = SCROLLX >> 3; + m_layer[0].xshift = SCROLLX & 7; + m_layer[0].xstart = 0; + m_layer[0].xend = 160; + } + + if (m_layer[1].enabled) + { + int xpos; + + /* Window X position is offset by 7 so we'll need to adust */ + xpos = WNDPOSX - 7; + if (xpos < 0) + xpos = 0; + + m_layer[1].bgline = m_window_lines_drawn; + m_layer[1].bg_map = m_vram + m_gb_wndtab_offs; + m_layer[1].gbc_map = m_vram + m_gbc_wndtab_offs; + m_layer[1].xindex = 0; + m_layer[1].xshift = 0; + m_layer[1].xstart = xpos; + m_layer[1].xend = 160; + m_layer[0].xend = xpos; + } + m_start_x = 0; + } + + if (cycles_to_go < 160) + { + m_end_x = MIN(160 - cycles_to_go, 160); + /* Draw empty line when the background is disabled */ + if (!(LCDCONT & 0x01)) + { + rectangle r(m_start_x, m_end_x - 1, m_current_line, m_current_line); + bitmap.fill((!m_gbc_mode) ? 0 : 32767, r); + } + while (l < 2) + { + UINT8 xindex, *map, *tiles, *gbcmap; + UINT16 data; + int i, tile_index; + + if (!m_layer[l].enabled) + { + l++; + continue; + } + map = m_layer[l].bg_map + ((m_layer[l].bgline << 2) & 0x3E0); + gbcmap = m_layer[l].gbc_map + ((m_layer[l].bgline << 2) & 0x3E0); + tiles = (gbcmap[m_layer[l].xindex] & 0x08) ? (m_vram + m_gbc_chrgen_offs) : (m_vram + m_gb_chrgen_offs); + + /* Check for vertical flip */ + if (gbcmap[m_layer[l].xindex] & 0x40) + { + tiles += ((7 - (m_layer[l].bgline & 0x07)) << 1); + } + else + { + tiles += ((m_layer[l].bgline & 0x07) << 1); + } + xindex = m_start_x; + if (xindex < m_layer[l].xstart) + xindex = m_layer[l].xstart; + i = m_end_x; + if (i > m_layer[l].xend) + i = m_layer[l].xend; + i = i - xindex; + + tile_index = (map[m_layer[l].xindex] ^ m_gb_tile_no_mod) * 16; + data = tiles[tile_index] | (tiles[tile_index + 1] << 8); + /* Check for horinzontal flip */ + if (gbcmap[m_layer[l].xindex] & 0x20) + { + data >>= m_layer[l].xshift; + } + else + { + data <<= m_layer[l].xshift; + } + + while (i > 0) + { + while ((m_layer[l].xshift < 8) && i) + { + int colour; + /* Check for horinzontal flip */ + if (gbcmap[m_layer[l].xindex] & 0x20) + { + colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + data >>= 1; + } + else + { + colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + data <<= 1; + } + plot_pixel(bitmap, xindex, m_current_line, m_cgb_bpal[(!m_gbc_mode) ? m_gb_bpal[colour] : (((gbcmap[m_layer[l].xindex] & 0x07) * 4) + colour)]); + m_bg_zbuf[xindex] = colour + (gbcmap[m_layer[l].xindex] & 0x80); + xindex++; + m_layer[l].xshift++; + i--; + } + if (m_layer[l].xshift == 8) + { + /* Take possible changes to SCROLLY into account */ + if (l == 0) + { + m_layer[0].bgline = (SCROLLY + m_current_line) & 0xFF; + map = m_layer[l].bg_map + ((m_layer[l].bgline << 2) & 0x3E0); + gbcmap = m_layer[l].gbc_map + ((m_layer[l].bgline << 2) & 0x3E0); + } + + m_layer[l].xindex = (m_layer[l].xindex + 1) & 31; + m_layer[l].xshift = 0; + tiles = (gbcmap[m_layer[l].xindex] & 0x08) ? (m_vram + m_gbc_chrgen_offs) : (m_vram + m_gb_chrgen_offs); + + /* Check for vertical flip */ + if (gbcmap[m_layer[l].xindex] & 0x40) + { + tiles += ((7 - (m_layer[l].bgline & 0x07)) << 1); + } + else + { + tiles += ((m_layer[l].bgline & 0x07) << 1); + } + tile_index = (map[m_layer[l].xindex] ^ m_gb_tile_no_mod) * 16; + data = tiles[tile_index] | (tiles[tile_index + 1] << 8); + } + } + l++; + } + if (m_end_x == 160 && (LCDCONT & 0x02)) + { + update_sprites(); + } + m_start_x = m_end_x; + } + } + else + { + if (!(LCDCONT & 0x80)) + { + /* Draw an empty line when LCD is disabled */ + if (m_previous_line != m_current_line) + { + if (m_current_line < 144) + { + const rectangle &r1 = m_screen->visible_area(); + rectangle r(r1.min_x, r1.max_x, m_current_line, m_current_line); + bitmap.fill((!m_gbc_mode) ? 0 : 32767 , r); + } + m_previous_line = m_current_line; + } + } + } + + g_profiler.stop(); +} + + +TIMER_CALLBACK_MEMBER(gb_lcd_device::video_init_vbl) +{ + m_maincpu->set_input_line(VBL_INT, ASSERT_LINE); +} + +UINT32 gb_lcd_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) +{ + copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect); + return 0; +} + + +void gb_lcd_device::increment_scanline() +{ + m_current_line = (m_current_line + 1) % 154; + if (LCDCONT & 0x80) + { + CURLINE = m_current_line; + } + if (m_current_line == 0) + { + m_window_lines_drawn = 0; + } +} + +TIMER_CALLBACK_MEMBER(gb_lcd_device::lcd_timer_proc) +{ + static const int gb_sprite_cycles[] = { 0, 8, 20, 32, 44, 52, 64, 76, 88, 96, 108 }; + + m_state = param; + + if (LCDCONT & 0x80) + { + switch (m_state) + { + case GB_LCD_STATE_LYXX_PRE_M0: /* Just before switching to mode 0 */ + m_mode = 0; + if (LCDSTAT & 0x08) + { + if (!m_mode_irq) + { + if (!m_line_irq && !m_delayed_line_irq) + { + m_mode_irq = 1; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + else + { + m_mode_irq = 0; + } + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0); + break; + case GB_LCD_STATE_LYXX_M0: /* Switch to mode 0 */ + /* update current scanline */ + update_scanline(); + /* Increment the number of window lines drawn if enabled */ + if (m_layer[1].enabled) + { + m_window_lines_drawn++; + } + m_previous_line = m_current_line; + /* Set Mode 0 lcdstate */ + m_mode = 0; + LCDSTAT &= 0xFC; + m_oam_locked = UNLOCKED; + m_vram_locked = UNLOCKED; + /* + There seems to a kind of feature in the Game Boy hardware when the lowest bits of the + SCROLLX register equals 3 or 7, then the delayed M0 irq is triggered 4 cycles later + than usual. + The SGB probably has the same bug. + */ + if ((SCROLLX & 0x03) == 0x03) + { + m_scrollx_adjust += 4; + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0_SCX3); + break; + } + case GB_LCD_STATE_LYXX_M0_SCX3: + /* Generate lcd interrupt if requested */ + if (!m_mode_irq && (LCDSTAT & 0x08) && + ((!m_line_irq && m_delayed_line_irq) || !(LCDSTAT & 0x40))) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(196 - m_scrollx_adjust - m_sprite_cycles), GB_LCD_STATE_LYXX_M0_PRE_INC); + break; + case GB_LCD_STATE_LYXX_M0_PRE_INC: /* Just before incrementing the line counter go to mode 2 internally */ + if (CURLINE < 143) + { + m_mode = 2; + m_triggering_mode_irq = (LCDSTAT & 0x20) ? 1 : 0; + if (m_triggering_mode_irq) + { + if (!m_mode_irq) + { + if (!m_line_irq && !m_delayed_line_irq) + { + m_mode_irq = 1; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + else + { + m_mode_irq = 0; + } + } + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0_INC); + break; + case GB_LCD_STATE_LYXX_M0_INC: /* Increment LY, stay in M0 for 4 more cycles */ + increment_scanline(); + m_delayed_line_irq = m_line_irq; + m_triggering_line_irq = ((CMPLINE == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; + m_line_irq = 0; + if (!m_mode_irq && !m_delayed_line_irq && m_triggering_line_irq && !m_triggering_mode_irq) + { + m_line_irq = m_triggering_line_irq; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + /* Reset LY==LYC STAT bit */ + LCDSTAT &= 0xFB; + /* Check if we're going into VBlank next */ + if (CURLINE == 144) + { + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY9X_M1); + } + else + { + /* Internally switch to mode 2 */ + m_mode = 2; + /* Generate lcd interrupt if requested */ + if (!m_mode_irq && m_triggering_mode_irq && + ((!m_triggering_line_irq && !m_delayed_line_irq) || !(LCDSTAT & 0x40))) + { + m_mode_irq = 1; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M2); + } + break; + case GB_LCD_STATE_LY00_M2: /* Switch to mode 2 on line #0 */ + /* Set Mode 2 lcdstate */ + m_mode = 2; + LCDSTAT = (LCDSTAT & 0xFC) | 0x02; + m_oam_locked = LOCKED; + /* Generate lcd interrupt if requested */ + if ((LCDSTAT & 0x20) && !m_line_irq) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + /* Check for regular compensation of x-scroll register */ + m_scrollx_adjust = (SCROLLX & 0x04) ? 4 : 0; + /* Mode 2 lasts approximately 80 clock cycles */ + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(80), GB_LCD_STATE_LYXX_M3); + break; + case GB_LCD_STATE_LYXX_M2: /* Switch to mode 2 */ + /* Update STAT register to the correct state */ + LCDSTAT = (LCDSTAT & 0xFC) | 0x02; + m_oam_locked = LOCKED; + /* Generate lcd interrupt if requested */ + if ((m_delayed_line_irq && m_triggering_line_irq && !(LCDSTAT & 0x20)) || + (!m_mode_irq && !m_line_irq && !m_delayed_line_irq && m_triggering_mode_irq)) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + m_line_irq = m_triggering_line_irq; + m_triggering_mode_irq = 0; + /* Check if LY==LYC STAT bit should be set */ + if (CURLINE == CMPLINE) + { + LCDSTAT |= 0x04; + } + /* Check for regular compensation of x-scroll register */ + m_scrollx_adjust = (SCROLLX & 0x04) ? 4 : 0; + /* Mode 2 last for approximately 80 clock cycles */ + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(80), GB_LCD_STATE_LYXX_M3); + break; + case GB_LCD_STATE_LYXX_M3: /* Switch to mode 3 */ + select_sprites(); + m_sprite_cycles = gb_sprite_cycles[m_sprCount]; + /* Set Mode 3 lcdstate */ + m_mode = 3; + LCDSTAT = (LCDSTAT & 0xFC) | 0x03; + m_vram_locked = LOCKED; + /* Check for compensations of x-scroll register */ + /* Mode 3 lasts for approximately 172+cycles needed to handle sprites clock cycles */ + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(168 + m_scrollx_adjust + m_sprite_cycles), GB_LCD_STATE_LYXX_PRE_M0); + m_start_x = -1; + break; + case GB_LCD_STATE_LY9X_M1: /* Switch to or stay in mode 1 */ + if (CURLINE == 144) + { + /* Trigger VBlank interrupt */ + m_maincpu->set_input_line(VBL_INT, ASSERT_LINE); + /* Set VBlank lcdstate */ + m_mode = 1; + LCDSTAT = (LCDSTAT & 0xFC) | 0x01; + /* Trigger LCD interrupt if requested */ + if (LCDSTAT & 0x10) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + /* Check if LY==LYC STAT bit should be set */ + if (CURLINE == CMPLINE) + { + LCDSTAT |= 0x04; + } + if (m_delayed_line_irq && m_triggering_line_irq) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(452), GB_LCD_STATE_LY9X_M1_INC); + break; + case GB_LCD_STATE_LY9X_M1_INC: /* Increment scanline counter */ + increment_scanline(); + m_delayed_line_irq = m_line_irq; + m_triggering_line_irq = ((CMPLINE == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; + m_line_irq = 0; + if (!m_delayed_line_irq && m_triggering_line_irq) + { + m_line_irq = m_triggering_line_irq; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + /* Reset LY==LYC STAT bit */ + LCDSTAT &= 0xFB; + if (m_current_line == 153) + { + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M1); + } + else + { + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY9X_M1); + } + break; + case GB_LCD_STATE_LY00_M1: /* we stay in VBlank but current line counter should already be incremented */ + /* Check LY=LYC for line #153 */ + if (m_delayed_line_irq) + { + if (m_triggering_line_irq) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + m_delayed_line_irq = m_delayed_line_irq | m_line_irq; + if (CURLINE == CMPLINE) + { + LCDSTAT |= 0x04; + } + increment_scanline(); + m_triggering_line_irq = ((CMPLINE == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; + m_line_irq = 0; + LCDSTAT &= 0xFB; + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4/*8*/), GB_LCD_STATE_LY00_M1_1); + break; + case GB_LCD_STATE_LY00_M1_1: + if (!m_delayed_line_irq && m_triggering_line_irq) + { + m_line_irq = m_triggering_line_irq; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M1_2); + break; + case GB_LCD_STATE_LY00_M1_2: /* Rest of line #0 during VBlank */ + if (m_delayed_line_irq && m_triggering_line_irq) + { + m_line_irq = m_triggering_line_irq; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + if (CURLINE == CMPLINE) + { + LCDSTAT |= 0x04; + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(444), GB_LCD_STATE_LY00_M0); + break; + case GB_LCD_STATE_LY00_M0: /* The STAT register seems to go to 0 for about 4 cycles */ + /* Set Mode 0 lcdstat */ + m_mode = 0; + LCDSTAT = (LCDSTAT & 0xFC); + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M2); + break; + } + } + else + { + increment_scanline(); + if (m_current_line < 144) + { + update_scanline(); + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(456)); + } +} + + +// CGB specific code + +void cgb_lcd_device::hdma_trans(UINT16 length) +{ + UINT16 src, dst; + address_space &space = m_maincpu->space(AS_PROGRAM); + + src = ((UINT16)HDMA1 << 8) | (HDMA2 & 0xF0); + dst = ((UINT16)(HDMA3 & 0x1F) << 8) | (HDMA4 & 0xF0); + dst |= 0x8000; + while (length > 0) + { + space.write_byte(dst++, space.read_byte(src++)); + length--; + } + HDMA1 = src >> 8; + HDMA2 = src & 0xF0; + HDMA3 = 0x1f & (dst >> 8); + HDMA4 = dst & 0xF0; + HDMA5--; + if ((HDMA5 & 0x7f) == 0x7f) + { + HDMA5 = 0xff; + m_hdma_enabled = 0; + } +} + + +TIMER_CALLBACK_MEMBER(cgb_lcd_device::lcd_timer_proc) +{ + static const int cgb_sprite_cycles[] = { 0, 8, 20, 32, 44, 52, 64, 76, 88, 96, 108 }; + + m_state = param; + + if (LCDCONT & 0x80) + { + switch (m_state) + { + case GB_LCD_STATE_LYXX_PRE_M0: /* Just before switching to mode 0 */ + m_mode = 0; + if (LCDSTAT & 0x08) + { + if (!m_mode_irq) + { + if (!m_line_irq && !m_delayed_line_irq) + { + m_mode_irq = 1; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + else + { + m_mode_irq = 0; + } + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0); + break; + case GB_LCD_STATE_LYXX_M0: /* Switch to mode 0 */ + /* update current scanline */ + update_scanline(); + /* Increment the number of window lines drawn if enabled */ + if (m_layer[1].enabled) + { + m_window_lines_drawn++; + } + m_previous_line = m_current_line; + /* Set Mode 0 lcdstate */ + m_mode = 0; + LCDSTAT &= 0xFC; + m_oam_locked = UNLOCKED; + m_vram_locked = UNLOCKED; + /* + There seems to a kind of feature in the Game Boy hardware when the lowest bits of the + SCROLLX register equals 3 or 7, then the delayed M0 irq is triggered 4 cycles later + than usual. + The SGB probably has the same bug. + */ + m_triggering_mode_irq = (LCDSTAT & 0x08) ? 1 : 0; + if ((SCROLLX & 0x03) == 0x03) + { + m_scrollx_adjust += 4; + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0_SCX3); + break; + } + case GB_LCD_STATE_LYXX_M0_SCX3: + /* Generate lcd interrupt if requested */ + if (!m_mode_irq && m_triggering_mode_irq && + ((!m_line_irq && m_delayed_line_irq) || !(LCDSTAT & 0x40))) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + m_triggering_mode_irq = 0; + } + if ((SCROLLX & 0x03) == 0x03) + { + m_pal_locked = UNLOCKED; + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0_GBC_PAL); + break; + case GB_LCD_STATE_LYXX_M0_GBC_PAL: + m_pal_locked = UNLOCKED; + /* Check for HBLANK DMA */ + if (m_hdma_enabled) + { + hdma_trans(0x10); +// cpunum_set_reg(0, LR35902_DMA_CYCLES, 36); + } + else + { + m_hdma_possible = 1; + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(192 - m_scrollx_adjust - m_sprite_cycles), GB_LCD_STATE_LYXX_M0_PRE_INC); + break; + case GB_LCD_STATE_LYXX_M0_PRE_INC: /* Just before incrementing the line counter go to mode 2 internally */ + m_cmp_line = CMPLINE; + if (CURLINE < 143) + { + m_mode = 2; + if (LCDSTAT & 0x20) + { + if (!m_mode_irq) + { + if (!m_line_irq && !m_delayed_line_irq) + { + m_mode_irq = 1; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + else + { + m_mode_irq = 0; + } + } + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0_INC); + break; + case GB_LCD_STATE_LYXX_M0_INC: /* Increment LY, stay in M0 for 4 more cycles */ + increment_scanline(); + m_delayed_line_irq = m_line_irq; + m_triggering_line_irq = ((m_cmp_line == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; + m_line_irq = 0; + if (!m_mode_irq && !m_delayed_line_irq && m_triggering_line_irq && !(LCDSTAT & 0x20)) + { + m_line_irq = m_triggering_line_irq; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + m_hdma_possible = 0; + /* Check if we're going into VBlank next */ + if (CURLINE == 144) + { + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY9X_M1); + } + else + { + /* Internally switch to mode 2 */ + m_mode = 2; + /* Generate lcd interrupt if requested */ + if (!m_mode_irq && (LCDSTAT & 0x20) && + ((!m_triggering_line_irq && !m_delayed_line_irq) || !(LCDSTAT & 0x40))) + { + m_mode_irq = 1; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M2); + } + break; + case GB_LCD_STATE_LY00_M2: /* Switch to mode 2 on line #0 */ + /* Set Mode 2 lcdstate */ + m_mode = 2; + LCDSTAT = (LCDSTAT & 0xFC) | 0x02; + m_oam_locked = LOCKED; + /* Generate lcd interrupt if requested */ + if ((LCDSTAT & 0x20) && !m_line_irq) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + /* Check for regular compensation of x-scroll register */ + m_scrollx_adjust = (SCROLLX & 0x04) ? 4 : 0; + /* Mode 2 lasts approximately 80 clock cycles */ + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(80), GB_LCD_STATE_LYXX_M3); + break; + case GB_LCD_STATE_LYXX_M2: /* Switch to mode 2 */ + /* Update STAT register to the correct state */ + LCDSTAT = (LCDSTAT & 0xFC) | 0x02; + m_oam_locked = LOCKED; + /* Generate lcd interrupt if requested */ + if ((m_delayed_line_irq && m_triggering_line_irq && !(LCDSTAT & 0x20)) || + (!m_mode_irq && !m_line_irq && !m_delayed_line_irq && (LCDSTAT & 0x20))) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + m_line_irq = m_triggering_line_irq; + /* Check if LY==LYC STAT bit should be set */ + if (CURLINE == CMPLINE) + { + LCDSTAT |= 0x04; + } + else + { + LCDSTAT &= ~0x04; + } + /* Check for regular compensation of x-scroll register */ + m_scrollx_adjust = (SCROLLX & 0x04) ? 4 : 0; + /* Mode 2 last for approximately 80 clock cycles */ + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(80), GB_LCD_STATE_LYXX_M3); + break; + case GB_LCD_STATE_LYXX_M3: /* Switch to mode 3 */ + select_sprites(); + m_sprite_cycles = cgb_sprite_cycles[m_sprCount]; + /* Set Mode 3 lcdstate */ + m_mode = 3; + LCDSTAT = (LCDSTAT & 0xFC) | 0x03; + m_vram_locked = LOCKED; + m_pal_locked = LOCKED; + /* Check for compensations of x-scroll register */ + /* Mode 3 lasts for approximately 172+cycles needed to handle sprites clock cycles */ + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(168 + m_scrollx_adjust + m_sprite_cycles), GB_LCD_STATE_LYXX_PRE_M0); + m_start_x = -1; + break; + case GB_LCD_STATE_LY9X_M1: /* Switch to or stay in mode 1 */ + if (CURLINE == 144) + { + /* Trigger VBlank interrupt */ + m_maincpu->set_input_line(VBL_INT, ASSERT_LINE); + /* Set VBlank lcdstate */ + m_mode = 1; + LCDSTAT = (LCDSTAT & 0xFC) | 0x01; + /* Trigger LCD interrupt if requested */ + if (LCDSTAT & 0x10) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + /* Check if LY==LYC STAT bit should be set */ + if (CURLINE == CMPLINE) + { + LCDSTAT |= 0x04; + } + else + { + LCDSTAT &= ~0x04; + } + if (m_delayed_line_irq && m_triggering_line_irq) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(452), GB_LCD_STATE_LY9X_M1_INC); + break; + case GB_LCD_STATE_LY9X_M1_INC: /* Increment scanline counter */ + increment_scanline(); + m_delayed_line_irq = m_line_irq; + m_triggering_line_irq = ((CMPLINE == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; + m_line_irq = 0; + if (!m_delayed_line_irq && m_triggering_line_irq) + { + m_line_irq = m_triggering_line_irq; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + if (m_current_line == 153) + { + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M1); + } + else + { + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY9X_M1); + } + break; + case GB_LCD_STATE_LY00_M1: /* we stay in VBlank but current line counter should already be incremented */ + /* Check LY=LYC for line #153 */ + if (m_delayed_line_irq) + { + if (m_triggering_line_irq) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + m_delayed_line_irq = m_delayed_line_irq | m_line_irq; + if (CURLINE == CMPLINE) + { + LCDSTAT |= 0x04; + } + else + { + LCDSTAT &= ~0x04; + } + increment_scanline(); + m_triggering_line_irq = ((CMPLINE == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; + m_line_irq = 0; + LCDSTAT &= 0xFB; + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M1_1); + break; + case GB_LCD_STATE_LY00_M1_1: + if (!m_delayed_line_irq && m_triggering_line_irq) + { + m_line_irq = m_triggering_line_irq; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M1_2); + break; + case GB_LCD_STATE_LY00_M1_2: /* Rest of line #0 during VBlank */ + if (m_delayed_line_irq && m_triggering_line_irq) + { + m_line_irq = m_triggering_line_irq; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + if (CURLINE == CMPLINE) + { + LCDSTAT |= 0x04; + } + else + { + LCDSTAT &= ~0x04; + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(444), GB_LCD_STATE_LY00_M0); + break; + case GB_LCD_STATE_LY00_M0: /* The STAT register seems to go to 0 for about 4 cycles */ + /* Set Mode 0 lcdstat */ + m_mode = 0; + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M2); + break; + } + } + else + { + increment_scanline(); + if (m_current_line < 144) + { + update_scanline(); + } + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(456)); + } +} + + +void gb_lcd_device::lcd_switch_on() +{ + m_current_line = 0; + m_previous_line = 153; + m_window_lines_drawn = 0; + m_line_irq = 0; + m_delayed_line_irq = 0; + m_mode = 0; + m_oam_locked = LOCKED; /* TODO: Investigate whether this OAM locking is correct. */ + /* Check for LY=LYC coincidence */ + if (CURLINE == CMPLINE) + { + LCDSTAT |= 0x04; + /* Generate lcd interrupt if requested */ + if (LCDSTAT & 0x40) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + m_state = GB_LCD_STATE_LY00_M2; + m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(80), GB_LCD_STATE_LYXX_M3); +} + + + + +READ8_MEMBER(gb_lcd_device::vram_r) +{ + return (m_vram_locked == LOCKED) ? 0xff : m_vram[offset + (m_vram_bank * 0x2000)]; +} + +WRITE8_MEMBER(gb_lcd_device::vram_w) +{ + if (m_vram_locked == LOCKED) + return; + + m_vram[offset + (m_vram_bank * 0x2000)] = data; +} + +READ8_MEMBER(gb_lcd_device::oam_r) +{ + return (m_oam_locked == LOCKED) ? 0xff : m_oam[offset]; +} + +WRITE8_MEMBER(gb_lcd_device::oam_w) +{ + if (m_oam_locked == LOCKED || offset >= 0xa0) + return; + + m_oam[offset] = data; +} + + + +READ8_MEMBER(gb_lcd_device::video_r) +{ + return m_vid_regs[offset]; +} + +WRITE8_MEMBER(gb_lcd_device::video_w) +{ + switch (offset) + { + case 0x00: /* LCDC - LCD Control */ + m_gb_chrgen_offs = (data & 0x10) ? 0x0000 : 0x0800; + m_gb_tile_no_mod = (data & 0x10) ? 0x00 : 0x80; + m_gb_bgdtab_offs = (data & 0x08) ? 0x1c00 : 0x1800; + m_gb_wndtab_offs = (data & 0x40) ? 0x1c00 : 0x1800; + /* if LCD controller is switched off, set STAT and LY to 00 */ + if (!(data & 0x80)) + { + LCDSTAT &= ~0x03; + CURLINE = 0; + m_oam_locked = UNLOCKED; + m_vram_locked = UNLOCKED; + } + /* If LCD is being switched on */ + if (!(LCDCONT & 0x80) && (data & 0x80)) + { + lcd_switch_on(); + } + break; + case 0x01: /* STAT - LCD Status */ + data = 0x80 | (data & 0x78) | (LCDSTAT & 0x07); + /* + Check for the STAT bug: + Writing to STAT when the LCD controller is active causes a STAT + interrupt to be triggered. + */ + if (LCDCONT & 0x80) + { + /* Triggers seen so far: + - 0x40 -> 0x00 - trigger + - 0x00 -> 0x08 - trigger + - 0x08 -> 0x00 - don't trigger + - 0x00 -> 0x20 (mode 3) - trigger + - 0x00 -> 0x60 (mode 2) - don't trigger + - 0x20 -> 0x60 (mode 3) - trigger + - 0x20 -> 0x40 (mode 3) - trigger + - 0x40 -> 0x20 (mode 2) - don't trigger + - 0x40 -> 0x08 (mode 0) - don't trigger + - 0x00 -> 0x40 - trigger only if LY==LYC + - 0x20 -> 0x00/0x08/0x10/0x20/0x40 (mode 2, after m2int) - don't trigger + - 0x20 -> 0x00/0x08/0x10/0x20/0x40 (mode 3, after m2int) - don't trigger + */ + if (!m_mode_irq && ((m_mode == 1) || + ((LCDSTAT & 0x40) && !(data & 0x68)) || + (!(LCDSTAT & 0x40) && (data & 0x40) && (LCDSTAT & 0x04)) || + (!(LCDSTAT & 0x48) && (data & 0x08)) || + ((LCDSTAT & 0x60) == 0x00 && (data & 0x60) == 0x20) || + ((LCDSTAT & 0x60) == 0x20 && (data & 0x40)) + )) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + /* + - 0x20 -> 0x08/0x18/0x28/0x48 (mode 0, after m2int) - trigger + - 0x20 -> 0x00/0x10/0x20/0x40 (mode 0, after m2int) - trigger (stat bug) + - 0x00 -> 0xXX (mode 0) - trigger stat bug + */ + if (m_mode_irq && m_mode == 0) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + break; + case 0x04: /* LY - LCD Y-coordinate */ + return; + case 0x05: /* LYC */ + if (CMPLINE != data) + { + if (CURLINE == data) + { + if (m_state != GB_LCD_STATE_LYXX_M0_INC && m_state != GB_LCD_STATE_LY9X_M1_INC) + { + LCDSTAT |= 0x04; + /* Generate lcd interrupt if requested */ + if (LCDSTAT & 0x40) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + } + else + { + LCDSTAT &= 0xFB; + m_triggering_line_irq = 0; + } + } + break; + case 0x06: /* DMA - DMA Transfer and Start Address */ + { + UINT8 *P = m_oam; + offset = (UINT16) data << 8; + for (data = 0; data < 0xA0; data++) + *P++ = space.read_byte(offset++); + } + return; + case 0x07: /* BGP - Background Palette */ + update_scanline(); + m_gb_bpal[0] = data & 0x3; + m_gb_bpal[1] = (data & 0xC) >> 2; + m_gb_bpal[2] = (data & 0x30) >> 4; + m_gb_bpal[3] = (data & 0xC0) >> 6; + break; + case 0x08: /* OBP0 - Object Palette 0 */ +// update_scanline(); + m_gb_spal0[0] = data & 0x3; + m_gb_spal0[1] = (data & 0xC) >> 2; + m_gb_spal0[2] = (data & 0x30) >> 4; + m_gb_spal0[3] = (data & 0xC0) >> 6; + break; + case 0x09: /* OBP1 - Object Palette 1 */ +// update_scanline(); + m_gb_spal1[0] = data & 0x3; + m_gb_spal1[1] = (data & 0xC) >> 2; + m_gb_spal1[2] = (data & 0x30) >> 4; + m_gb_spal1[3] = (data & 0xC0) >> 6; + break; + case 0x02: /* SCY - Scroll Y */ + case 0x03: /* SCX - Scroll X */ + update_scanline(); + break; + case 0x0A: /* WY - Window Y position */ + case 0x0B: /* WX - Window X position */ + break; + default: /* Unknown register, no change */ + return; + } + m_vid_regs[offset] = data; +} + +READ8_MEMBER(cgb_lcd_device::video_r) +{ + switch (offset) + { + case 0x11: /* FF51 */ + case 0x12: /* FF52 */ + case 0x13: /* FF53 */ + case 0x14: /* FF54 */ + return 0xFF; + case 0x29: /* FF69 */ + case 0x2B: /* FF6B */ + if (m_pal_locked == LOCKED) + { + return 0xFF; + } + break; + } + return m_vid_regs[offset]; +} + +WRITE8_MEMBER(cgb_lcd_device::video_w) +{ + switch (offset) + { + case 0x00: /* LCDC - LCD Control */ + m_gb_chrgen_offs = (data & 0x10) ? 0x0000 : 0x0800; + m_gbc_chrgen_offs = (data & 0x10) ? 0x2000 : 0x2800; + m_gb_tile_no_mod = (data & 0x10) ? 0x00 : 0x80; + m_gb_bgdtab_offs = (data & 0x08) ? 0x1c00 : 0x1800; + m_gbc_bgdtab_offs = (data & 0x08) ? 0x3c00 : 0x3800; + m_gb_wndtab_offs = (data & 0x40) ? 0x1c00 : 0x1800; + m_gbc_wndtab_offs = (data & 0x40) ? 0x3c00 : 0x3800; + /* if LCD controller is switched off, set STAT to 00 */ + if (!(data & 0x80)) + { + LCDSTAT &= ~0x03; + CURLINE = 0; + m_oam_locked = UNLOCKED; + m_vram_locked = UNLOCKED; + m_pal_locked = UNLOCKED; + } + /* If LCD is being switched on */ + if (!(LCDCONT & 0x80) && (data & 0x80)) + { + lcd_switch_on(); + } + break; + case 0x01: /* STAT - LCD Status */ + data = 0x80 | (data & 0x78) | (LCDSTAT & 0x07); + if (LCDCONT & 0x80) + { + /* + - 0x20 -> 0x08/0x18/0x28/0x48 (mode 0, after m2int) - trigger + */ + if (m_mode_irq && m_mode == 0 && (LCDSTAT & 0x28) == 0x20 && (data & 0x08)) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + /* Check if line irqs are being disabled */ + if (!(data & 0x40)) + { + m_delayed_line_irq = 0; + } + /* Check if line irqs are being enabled */ + if (!(LCDSTAT & 0x40) && (data & 0x40)) + { + if (CMPLINE == CURLINE) + { + m_line_irq = 1; + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + } + break; + case 0x05: /* LYC */ + if (CMPLINE != data) + { + if ((m_state != GB_LCD_STATE_LYXX_M0_PRE_INC && CURLINE == data) || + (m_state == GB_LCD_STATE_LYXX_M0_INC && m_triggering_line_irq)) + { + LCDSTAT |= 0x04; + /* Generate lcd interrupt if requested */ + if (LCDSTAT & 0x40) + { + m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + } + } + else + { + LCDSTAT &= 0xFB; + m_triggering_line_irq = 0; + m_cmp_line = data; + } + } + break; + case 0x07: /* BGP - GB background palette */ + update_scanline(); + m_gb_bpal[0] = data & 0x3; + m_gb_bpal[1] = (data & 0xC) >> 2; + m_gb_bpal[2] = (data & 0x30) >> 4; + m_gb_bpal[3] = (data & 0xC0) >> 6; + break; + case 0x08: /* OBP0 - GB Object 0 palette */ + m_gb_spal0[0] = data & 0x3; + m_gb_spal0[1] = (data & 0xC) >> 2; + m_gb_spal0[2] = (data & 0x30) >> 4; + m_gb_spal0[3] = (data & 0xC0) >> 6; + break; + case 0x09: /* OBP1 - GB Object 1 palette */ + m_gb_spal1[0] = data & 0x3; + m_gb_spal1[1] = (data & 0xC) >> 2; + m_gb_spal1[2] = (data & 0x30) >> 4; + m_gb_spal1[3] = (data & 0xC0) >> 6; + break; + case 0x0c: /* Undocumented register involved in selecting gb/gbc mode */ + logerror("Write to undocumented register: %X = %X\n", offset, data); + break; + case 0x0F: /* VBK - VRAM bank select */ + m_vram_bank = data & 0x01; + data |= 0xFE; + break; + case 0x11: /* HDMA1 - HBL General DMA - Source High */ + break; + case 0x12: /* HDMA2 - HBL General DMA - Source Low */ + data &= 0xF0; + break; + case 0x13: /* HDMA3 - HBL General DMA - Destination High */ + data &= 0x1F; + break; + case 0x14: /* HDMA4 - HBL General DMA - Destination Low */ + data &= 0xF0; + break; + case 0x15: /* HDMA5 - HBL General DMA - Mode, Length */ + if (!(data & 0x80)) + { + if (m_hdma_enabled) + { + m_hdma_enabled = 0; + data = HDMA5 & 0x80; + } + else + { + /* General DMA */ + hdma_trans(((data & 0x7F) + 1) * 0x10); +// cpunum_set_reg(0, LR35902_DMA_CYCLES, 4 + (((data & 0x7F) + 1) * 32)); + data = 0xff; + } + } + else + { + /* H-Blank DMA */ + m_hdma_enabled = 1; + data &= 0x7f; + m_vid_regs[offset] = data; + /* Check if HDMA should be immediately performed */ + if (m_hdma_possible) + { + hdma_trans(0x10); +// cpunum_set_reg(0, LR35902_DMA_CYCLES, 36); + m_hdma_possible = 0; + } + } + break; + case 0x28: /* BCPS - Background palette specification */ + GBCBCPS = data; + if (data & 0x01) + GBCBCPD = m_cgb_bpal[(data >> 1) & 0x1F] >> 8; + else + GBCBCPD = m_cgb_bpal[(data >> 1) & 0x1F] & 0xFF; + break; + case 0x29: /* BCPD - background palette data */ + if (m_pal_locked == LOCKED) + { + return; + } + GBCBCPD = data; + if (GBCBCPS & 0x01) + m_cgb_bpal[(GBCBCPS >> 1) & 0x1F] = ((data << 8) | (m_cgb_bpal[(GBCBCPS >> 1) & 0x1F] & 0xFF)) & 0x7FFF; + else + m_cgb_bpal[(GBCBCPS >> 1) & 0x1F] = ((m_cgb_bpal[(GBCBCPS >> 1) & 0x1F] & 0xFF00) | data) & 0x7FFF; + if (GBCBCPS & 0x80) + { + GBCBCPS++; + GBCBCPS &= 0xBF; + } + break; + case 0x2A: /* OCPS - Object palette specification */ + GBCOCPS = data; + if (data & 0x01) + GBCOCPD = m_cgb_spal[(data >> 1) & 0x1F] >> 8; + else + GBCOCPD = m_cgb_spal[(data >> 1) & 0x1F] & 0xFF; + break; + case 0x2B: /* OCPD - Object palette data */ + if (m_pal_locked == LOCKED) + { + return; + } + GBCOCPD = data; + if (GBCOCPS & 0x01) + m_cgb_spal[(GBCOCPS >> 1) & 0x1F] = ((data << 8) | (m_cgb_spal[(GBCOCPS >> 1) & 0x1F] & 0xFF)) & 0x7FFF; + else + m_cgb_spal[(GBCOCPS >> 1) & 0x1F] = ((m_cgb_spal[(GBCOCPS >> 1) & 0x1F] & 0xFF00) | data) & 0x7FFF; + if (GBCOCPS & 0x80) + { + GBCOCPS++; + GBCOCPS &= 0xBF; + } + break; + /* Undocumented registers */ + case 0x2C: + /* bit 0 can be read/written */ + logerror("Write to undocumented register: %X = %X\n", offset, data); + data = 0xFE | (data & 0x01); + if (data & 0x01) + { + m_gbc_mode = 0; + } + break; + case 0x32: + case 0x33: + case 0x34: + /* whole byte can be read/written */ + logerror("Write to undocumented register: %X = %X\n", offset, data); + break; + case 0x35: + /* bit 4-6 can be read/written */ + logerror("Write to undocumented register: %X = %X\n", offset, data); + data = 0x8F | (data & 0x70); + break; + case 0x36: + case 0x37: + logerror("Write to undocumented register: %X = %X\n", offset, data); + return; + default: + /* we didn't handle the write, so pass it to the GB handler */ + gb_lcd_device::video_w(space, offset, data); + return; + } + + m_vid_regs[offset] = data; +} + +// Super Game Boy + +void sgb_lcd_device::sgb_io_write_pal(int offs, UINT8 *data) +{ + switch (offs) + { + case 0x00: /* PAL01 */ + m_sgb_pal[0 * 4 + 0] = data[1] | (data[2] << 8); + m_sgb_pal[0 * 4 + 1] = data[3] | (data[4] << 8); + m_sgb_pal[0 * 4 + 2] = data[5] | (data[6] << 8); + m_sgb_pal[0 * 4 + 3] = data[7] | (data[8] << 8); + m_sgb_pal[1 * 4 + 0] = data[1] | (data[2] << 8); + m_sgb_pal[1 * 4 + 1] = data[9] | (data[10] << 8); + m_sgb_pal[1 * 4 + 2] = data[11] | (data[12] << 8); + m_sgb_pal[1 * 4 + 3] = data[13] | (data[14] << 8); + break; + case 0x01: /* PAL23 */ + m_sgb_pal[2 * 4 + 0] = data[1] | (data[2] << 8); + m_sgb_pal[2 * 4 + 1] = data[3] | (data[4] << 8); + m_sgb_pal[2 * 4 + 2] = data[5] | (data[6] << 8); + m_sgb_pal[2 * 4 + 3] = data[7] | (data[8] << 8); + m_sgb_pal[3 * 4 + 0] = data[1] | (data[2] << 8); + m_sgb_pal[3 * 4 + 1] = data[9] | (data[10] << 8); + m_sgb_pal[3 * 4 + 2] = data[11] | (data[12] << 8); + m_sgb_pal[3 * 4 + 3] = data[13] | (data[14] << 8); + break; + case 0x02: /* PAL03 */ + m_sgb_pal[0 * 4 + 0] = data[1] | (data[2] << 8); + m_sgb_pal[0 * 4 + 1] = data[3] | (data[4] << 8); + m_sgb_pal[0 * 4 + 2] = data[5] | (data[6] << 8); + m_sgb_pal[0 * 4 + 3] = data[7] | (data[8] << 8); + m_sgb_pal[3 * 4 + 0] = data[1] | (data[2] << 8); + m_sgb_pal[3 * 4 + 1] = data[9] | (data[10] << 8); + m_sgb_pal[3 * 4 + 2] = data[11] | (data[12] << 8); + m_sgb_pal[3 * 4 + 3] = data[13] | (data[14] << 8); + break; + case 0x03: /* PAL12 */ + m_sgb_pal[1 * 4 + 0] = data[1] | (data[2] << 8); + m_sgb_pal[1 * 4 + 1] = data[3] | (data[4] << 8); + m_sgb_pal[1 * 4 + 2] = data[5] | (data[6] << 8); + m_sgb_pal[1 * 4 + 3] = data[7] | (data[8] << 8); + m_sgb_pal[2 * 4 + 0] = data[1] | (data[2] << 8); + m_sgb_pal[2 * 4 + 1] = data[9] | (data[10] << 8); + m_sgb_pal[2 * 4 + 2] = data[11] | (data[12] << 8); + m_sgb_pal[2 * 4 + 3] = data[13] | (data[14] << 8); + break; + case 0x04: /* ATTR_BLK */ + { + UINT8 I, J, K, o; + for( K = 0; K < data[1]; K++ ) + { + o = K * 6; + if( data[o + 2] & 0x1 ) + { + for( I = data[ o + 4]; I <= data[o + 6]; I++ ) + { + for( J = data[o + 5]; J <= data[o + 7]; J++ ) + { + m_sgb_pal_map[I][J] = data[o + 3] & 0x3; + } + } + } + } + } + break; + case 0x05: /* ATTR_LIN */ + { + UINT8 J, K; + if( data[1] > 15 ) + data[1] = 15; + for( K = 0; K < data[1]; K++ ) + { + if( data[K + 1] & 0x80 ) + { + for( J = 0; J < 20; J++ ) + { + m_sgb_pal_map[J][data[K + 1] & 0x1f] = (data[K + 1] & 0x60) >> 5; + } + } + else + { + for( J = 0; J < 18; J++ ) + { + m_sgb_pal_map[data[K + 1] & 0x1f][J] = (data[K + 1] & 0x60) >> 5; + } + } + } + } + break; + case 0x06: /* ATTR_DIV */ + { + UINT8 I, J; + if( data[1] & 0x40 ) /* Vertical */ + { + for( I = 0; I < data[2]; I++ ) + { + for( J = 0; J < 20; J++ ) + { + m_sgb_pal_map[J][I] = (data[1] & 0xC) >> 2; + } + } + for( J = 0; J < 20; J++ ) + { + m_sgb_pal_map[J][data[2]] = (data[1] & 0x30) >> 4; + } + for( I = data[2] + 1; I < 18; I++ ) + { + for( J = 0; J < 20; J++ ) + { + m_sgb_pal_map[J][I] = data[1] & 0x3; + } + } + } + else /* Horizontal */ + { + for( I = 0; I < data[2]; I++ ) + { + for( J = 0; J < 18; J++ ) + { + m_sgb_pal_map[I][J] = (data[1] & 0xC) >> 2; + } + } + for( J = 0; J < 18; J++ ) + { + m_sgb_pal_map[data[2]][J] = (data[1] & 0x30) >> 4; + } + for( I = data[2] + 1; I < 20; I++ ) + { + for( J = 0; J < 18; J++ ) + { + m_sgb_pal_map[I][J] = data[1] & 0x3; + } + } + } + } + break; + case 0x07: /* ATTR_CHR */ + { + UINT16 I, sets; + UINT8 x, y; + sets = (data[3] | (data[4] << 8) ); + if( sets > 360 ) + sets = 360; + sets >>= 2; + sets += 6; + x = data[1]; + y = data[2]; + if( data[5] ) /* Vertical */ + { + for( I = 6; I < sets; I++ ) + { + m_sgb_pal_map[x][y++] = (data[I] & 0xC0) >> 6; + if( y > 17 ) + { + y = 0; + x++; + if( x > 19 ) + x = 0; + } + + m_sgb_pal_map[x][y++] = (data[I] & 0x30) >> 4; + if( y > 17 ) + { + y = 0; + x++; + if( x > 19 ) + x = 0; + } + + m_sgb_pal_map[x][y++] = (data[I] & 0xC) >> 2; + if( y > 17 ) + { + y = 0; + x++; + if( x > 19 ) + x = 0; + } + + m_sgb_pal_map[x][y++] = data[I] & 0x3; + if( y > 17 ) + { + y = 0; + x++; + if( x > 19 ) + x = 0; + } + } + } + else /* horizontal */ + { + for( I = 6; I < sets; I++ ) + { + m_sgb_pal_map[x++][y] = (data[I] & 0xC0) >> 6; + if( x > 19 ) + { + x = 0; + y++; + if( y > 17 ) + y = 0; + } + + m_sgb_pal_map[x++][y] = (data[I] & 0x30) >> 4; + if( x > 19 ) + { + x = 0; + y++; + if( y > 17 ) + y = 0; + } + + m_sgb_pal_map[x++][y] = (data[I] & 0xC) >> 2; + if( x > 19 ) + { + x = 0; + y++; + if( y > 17 ) + y = 0; + } + + m_sgb_pal_map[x++][y] = data[I] & 0x3; + if( x > 19 ) + { + x = 0; + y++; + if( y > 17 ) + y = 0; + } + } + } + } + break; + case 0x08: /* SOUND */ + /* This command enables internal sound effects */ + /* Not Implemented */ + break; + case 0x09: /* SOU_TRN */ + /* This command sends data to the SNES sound processor. + We'll need to emulate that for this to be used */ + /* Not Implemented */ + break; + case 0x0A: /* PAL_SET */ + { + UINT16 index_; + + /* Palette 0 */ + index_ = (UINT16)(data[1] | (data[2] << 8)) * 4; + m_sgb_pal[0] = m_sgb_pal_data[index_]; + m_sgb_pal[1] = m_sgb_pal_data[index_ + 1]; + m_sgb_pal[2] = m_sgb_pal_data[index_ + 2]; + m_sgb_pal[3] = m_sgb_pal_data[index_ + 3]; + /* Palette 1 */ + index_ = (UINT16)(data[3] | (data[4] << 8)) * 4; + m_sgb_pal[4] = m_sgb_pal_data[index_]; + m_sgb_pal[5] = m_sgb_pal_data[index_ + 1]; + m_sgb_pal[6] = m_sgb_pal_data[index_ + 2]; + m_sgb_pal[7] = m_sgb_pal_data[index_ + 3]; + /* Palette 2 */ + index_ = (UINT16)(data[5] | (data[6] << 8)) * 4; + m_sgb_pal[8] = m_sgb_pal_data[index_]; + m_sgb_pal[9] = m_sgb_pal_data[index_ + 1]; + m_sgb_pal[10] = m_sgb_pal_data[index_ + 2]; + m_sgb_pal[11] = m_sgb_pal_data[index_ + 3]; + /* Palette 3 */ + index_ = (UINT16)(data[7] | (data[8] << 8)) * 4; + m_sgb_pal[12] = m_sgb_pal_data[index_]; + m_sgb_pal[13] = m_sgb_pal_data[index_ + 1]; + m_sgb_pal[14] = m_sgb_pal_data[index_ + 2]; + m_sgb_pal[15] = m_sgb_pal_data[index_ + 3]; + /* Attribute File */ + if (data[9] & 0x40) + m_sgb_window_mask = 0; + m_sgb_atf = (data[9] & 0x3f) * (18 * 5); + if (data[9] & 0x80) + { + for (int j = 0; j < 18; j++ ) + { + for (int i = 0; i < 5; i++ ) + { + m_sgb_pal_map[i * 4][j] = (m_sgb_atf_data[(j * 5) + m_sgb_atf + i] & 0xC0) >> 6; + m_sgb_pal_map[(i * 4) + 1][j] = (m_sgb_atf_data[(j * 5) + m_sgb_atf + i] & 0x30) >> 4; + m_sgb_pal_map[(i * 4) + 2][j] = (m_sgb_atf_data[(j * 5) + m_sgb_atf + i] & 0xC) >> 2; + m_sgb_pal_map[(i * 4) + 3][j] = m_sgb_atf_data[(j * 5) + m_sgb_atf + i] & 0x3; + } + } + } + } + break; + case 0x0B: /* PAL_TRN */ + { + UINT16 col; + + for (int i = 0; i < 2048; i++ ) + { + col = (m_vram[0x0800 + (i * 2) + 1] << 8) | m_vram[0x0800 + (i * 2)]; + m_sgb_pal_data[i] = col; + } + } + break; + case 0x0C: /* ATRC_EN */ + /* Not Implemented */ + break; + case 0x0D: /* TEST_EN */ + /* Not Implemented */ + break; + case 0x0E: /* ICON_EN */ + /* Not Implemented */ + break; + case 0x0F: /* DATA_SND */ + /* Not Implemented */ + break; + case 0x10: /* DATA_TRN */ + /* Not Implemented */ + break; + case 0x12: /* JUMP */ + /* Not Implemented */ + break; + case 0x13: /* CHR_TRN */ + if (data[1] & 0x1) + memcpy(m_sgb_tile_data + 4096, m_vram + 0x0800, 4096); + else + memcpy(m_sgb_tile_data, m_vram + 0x0800, 4096); + break; + case 0x14: /* PCT_TRN */ + { + UINT16 col; + if (m_sgb_border_hack) + { + memcpy(m_sgb_tile_map, m_vram + 0x1000, 2048); + for (int i = 0; i < 64; i++) + { + col = (m_vram[0x0800 + (i * 2) + 1 ] << 8) | m_vram[0x0800 + (i * 2)]; + m_sgb_pal[SGB_BORDER_PAL_OFFSET + i] = col; + } + } + else /* Do things normally */ + { + memcpy(m_sgb_tile_map, m_vram + 0x0800, 2048); + for (int i = 0; i < 64; i++) + { + col = (m_vram[0x1000 + (i * 2) + 1] << 8) | m_vram[0x1000 + (i * 2)]; + m_sgb_pal[SGB_BORDER_PAL_OFFSET + i] = col; + } + } + } + break; + case 0x15: /* ATTR_TRN */ + memcpy(m_sgb_atf_data, m_vram + 0x0800, 4050); + break; + case 0x16: /* ATTR_SET */ + { + /* Attribute File */ + if (data[1] & 0x40) + m_sgb_window_mask = 0; + m_sgb_atf = (data[1] & 0x3f) * (18 * 5); + for (int j = 0; j < 18; j++) + { + for (int i = 0; i < 5; i++) + { + m_sgb_pal_map[i * 4][j] = (m_sgb_atf_data[(j * 5) + m_sgb_atf + i] & 0xC0) >> 6; + m_sgb_pal_map[(i * 4) + 1][j] = (m_sgb_atf_data[(j * 5) + m_sgb_atf + i] & 0x30) >> 4; + m_sgb_pal_map[(i * 4) + 2][j] = (m_sgb_atf_data[(j * 5) + m_sgb_atf + i] & 0xC) >> 2; + m_sgb_pal_map[(i * 4) + 3][j] = m_sgb_atf_data[(j * 5) + m_sgb_atf + i] & 0x3; + } + } + } + break; + case 0x17: /* MASK_EN */ + m_sgb_window_mask = data[1]; + break; + case 0x18: /* OBJ_TRN */ + /* Not Implemnted */ + break; + case 0x19: /* ? */ + /* Called by: dkl,dkl2,dkl3,zeldadx + But I don't know what it is for. */ + /* Not Implemented */ + break; + case 0x1E: /* Used by bootrom to transfer the gb cart header */ + break; + case 0x1F: /* Used by bootrom to transfer the gb cart header */ + break; + default: + logerror( "SGB: Unknown Command 0x%02x!\n", data[0] >> 3 ); + } + +} |