/**************************************************************************** * * * Functions to emulate the TMS34061 video controller * * * * Created by Zsolt Vasvari on 5/26/1998. * * Updated by Aaron Giles on 11/21/2000. * * * * This is far from complete. See the TMS34061 User's Guide available on * * www.spies.com/arcade * * * ****************************************************************************/ #include "driver.h" #include "tms34061.h" #define VERBOSE (0) /************************************* * * Internal structure * *************************************/ struct tms34061_data { UINT16 regs[TMS34061_REGCOUNT]; UINT16 xmask; UINT8 yshift; UINT32 vrammask; UINT8 * vram; UINT8 * latchram; UINT8 latchdata; UINT8 * shiftreg; mame_timer * timer; struct tms34061_interface intf; }; /************************************* * * Global variables * *************************************/ static struct tms34061_data tms34061; static const char *regnames[] = { "HORENDSYNC", "HORENDBLNK", "HORSTARTBLNK", "HORTOTAL", "VERENDSYNC", "VERENDBLNK", "VERSTARTBLNK", "VERTOTAL", "DISPUPDATE", "DISPSTART", "VERINT", "CONTROL1", "CONTROL2", "STATUS", "XYOFFSET", "XYADDRESS", "DISPADDRESS", "VERCOUNTER" }; /************************************* * * Prototypes * *************************************/ static TIMER_CALLBACK( tms34061_interrupt ); /************************************* * * Hardware startup * *************************************/ void tms34061_start(struct tms34061_interface *interface) { /* reset the data */ memset(&tms34061, 0, sizeof(tms34061)); tms34061.intf = *interface; tms34061.vrammask = tms34061.intf.vramsize - 1; /* allocate memory for VRAM */ tms34061.vram = auto_malloc(tms34061.intf.vramsize + 256 * 2); memset(tms34061.vram, 0, tms34061.intf.vramsize + 256 * 2); /* allocate memory for latch RAM */ tms34061.latchram = auto_malloc(tms34061.intf.vramsize + 256 * 2); memset(tms34061.latchram, 0, tms34061.intf.vramsize + 256 * 2); /* add some buffer space for VRAM and latch RAM */ tms34061.vram += 256; tms34061.latchram += 256; /* point the shift register to the base of VRAM for now */ tms34061.shiftreg = tms34061.vram; /* initialize registers to their default values from the manual */ tms34061.regs[TMS34061_HORENDSYNC] = 0x0010; tms34061.regs[TMS34061_HORENDBLNK] = 0x0020; tms34061.regs[TMS34061_HORSTARTBLNK] = 0x01f0; tms34061.regs[TMS34061_HORTOTAL] = 0x0200; tms34061.regs[TMS34061_VERENDSYNC] = 0x0004; tms34061.regs[TMS34061_VERENDBLNK] = 0x0010; tms34061.regs[TMS34061_VERSTARTBLNK] = 0x00f0; tms34061.regs[TMS34061_VERTOTAL] = 0x0100; tms34061.regs[TMS34061_DISPUPDATE] = 0x0000; tms34061.regs[TMS34061_DISPSTART] = 0x0000; tms34061.regs[TMS34061_VERINT] = 0x0000; tms34061.regs[TMS34061_CONTROL1] = 0x7000; tms34061.regs[TMS34061_CONTROL2] = 0x0600; tms34061.regs[TMS34061_STATUS] = 0x0000; tms34061.regs[TMS34061_XYOFFSET] = 0x0010; tms34061.regs[TMS34061_XYADDRESS] = 0x0000; tms34061.regs[TMS34061_DISPADDRESS] = 0x0000; tms34061.regs[TMS34061_VERCOUNTER] = 0x0000; /* start vertical interrupt timer */ tms34061.timer = mame_timer_alloc(tms34061_interrupt); } /************************************* * * Interrupt handling * *************************************/ INLINE void update_interrupts(void) { /* if we have a callback, process it */ if (tms34061.intf.interrupt) { /* if the status bit is set, and ints are enabled, turn it on */ if ((tms34061.regs[TMS34061_STATUS] & 0x0001) && (tms34061.regs[TMS34061_CONTROL1] & 0x0400)) (*tms34061.intf.interrupt)(ASSERT_LINE); else (*tms34061.intf.interrupt)(CLEAR_LINE); } } static TIMER_CALLBACK( tms34061_interrupt ) { /* set timer for next frame */ mame_timer_adjust(tms34061.timer, video_screen_get_frame_period(tms34061.intf.scrnum), 0, time_zero); /* set the interrupt bit in the status reg */ tms34061.regs[TMS34061_STATUS] |= 1; /* update the interrupt state */ update_interrupts(); } /************************************* * * Register writes * *************************************/ static WRITE8_HANDLER( register_w ) { int scanline; int regnum = offset >> 2; /* certain registers affect the display directly */ if ((regnum >= TMS34061_HORENDSYNC && regnum <= TMS34061_DISPSTART) || (regnum == TMS34061_CONTROL2)) video_screen_update_partial(0, video_screen_get_vpos(0)); /* store the hi/lo half */ if (offset & 0x02) tms34061.regs[regnum] = (tms34061.regs[regnum] & 0x00ff) | (data << 8); else tms34061.regs[regnum] = (tms34061.regs[regnum] & 0xff00) | data; /* log it */ if (VERBOSE) logerror("%04X:tms34061 %s = %04x\n", activecpu_get_pc(), regnames[regnum], tms34061.regs[regnum]); /* update the state of things */ switch (regnum) { /* vertical interrupt: adjust the timer */ case TMS34061_VERINT: scanline = tms34061.regs[TMS34061_VERINT] - tms34061.regs[TMS34061_VERENDBLNK]; if (scanline < 0) scanline += tms34061.regs[TMS34061_VERTOTAL]; mame_timer_adjust(tms34061.timer, video_screen_get_time_until_pos(tms34061.intf.scrnum, scanline, tms34061.regs[TMS34061_HORSTARTBLNK]), 0, time_zero); break; /* XY offset: set the X and Y masks */ case TMS34061_XYOFFSET: switch (tms34061.regs[TMS34061_XYOFFSET] & 0x00ff) { case 0x01: tms34061.yshift = 2; break; case 0x02: tms34061.yshift = 3; break; case 0x04: tms34061.yshift = 4; break; case 0x08: tms34061.yshift = 5; break; case 0x10: tms34061.yshift = 6; break; case 0x20: tms34061.yshift = 7; break; case 0x40: tms34061.yshift = 8; break; case 0x80: tms34061.yshift = 9; break; default: logerror("Invalid value for XYOFFSET = %04x\n", tms34061.regs[TMS34061_XYOFFSET]); break; } tms34061.xmask = (1 << tms34061.yshift) - 1; break; /* CONTROL1: they could have turned interrupts on */ case TMS34061_CONTROL1: update_interrupts(); break; /* other supported registers */ case TMS34061_XYADDRESS: break; } } /************************************* * * Register reads * *************************************/ static READ8_HANDLER( register_r ) { int regnum = offset >> 2; UINT16 result; /* extract the correct portion of the register */ result = tms34061.regs[regnum]; /* special cases: */ switch (regnum) { /* status register: a read here clears it */ case TMS34061_STATUS: tms34061.regs[TMS34061_STATUS] = 0; update_interrupts(); break; /* vertical count register: return the current scanline */ case TMS34061_VERCOUNTER: result = (video_screen_get_vpos(0)+ tms34061.regs[TMS34061_VERENDBLNK]) % tms34061.regs[TMS34061_VERTOTAL]; break; } /* log it */ if (VERBOSE) logerror("%04X:tms34061 %s read = %04X\n", activecpu_get_pc(), regnames[regnum], result); return (offset & 0x02) ? (result >> 8) : result; } /************************************* * * XY addressing * *************************************/ INLINE void adjust_xyaddress(int offset) { /* note that carries are allowed if the Y coordinate isn't being modified */ switch (offset & 0x1e) { case 0x00: /* no change */ break; case 0x02: /* X + 1 */ tms34061.regs[TMS34061_XYADDRESS]++; break; case 0x04: /* X - 1 */ tms34061.regs[TMS34061_XYADDRESS]--; break; case 0x06: /* X = 0 */ tms34061.regs[TMS34061_XYADDRESS] &= ~tms34061.xmask; break; case 0x08: /* Y + 1 */ tms34061.regs[TMS34061_XYADDRESS] += 1 << tms34061.yshift; break; case 0x0a: /* X + 1, Y + 1 */ tms34061.regs[TMS34061_XYADDRESS] = (tms34061.regs[TMS34061_XYADDRESS] & ~tms34061.xmask) | ((tms34061.regs[TMS34061_XYADDRESS] + 1) & tms34061.xmask); tms34061.regs[TMS34061_XYADDRESS] += 1 << tms34061.yshift; break; case 0x0c: /* X - 1, Y + 1 */ tms34061.regs[TMS34061_XYADDRESS] = (tms34061.regs[TMS34061_XYADDRESS] & ~tms34061.xmask) | ((tms34061.regs[TMS34061_XYADDRESS] - 1) & tms34061.xmask); tms34061.regs[TMS34061_XYADDRESS] += 1 << tms34061.yshift; break; case 0x0e: /* X = 0, Y + 1 */ tms34061.regs[TMS34061_XYADDRESS] &= ~tms34061.xmask; tms34061.regs[TMS34061_XYADDRESS] += 1 << tms34061.yshift; break; case 0x10: /* Y - 1 */ tms34061.regs[TMS34061_XYADDRESS] -= 1 << tms34061.yshift; break; case 0x12: /* X + 1, Y - 1 */ tms34061.regs[TMS34061_XYADDRESS] = (tms34061.regs[TMS34061_XYADDRESS] & ~tms34061.xmask) | ((tms34061.regs[TMS34061_XYADDRESS] + 1) & tms34061.xmask); tms34061.regs[TMS34061_XYADDRESS] -= 1 << tms34061.yshift; break; case 0x14: /* X - 1, Y - 1 */ tms34061.regs[TMS34061_XYADDRESS] = (tms34061.regs[TMS34061_XYADDRESS] & ~tms34061.xmask) | ((tms34061.regs[TMS34061_XYADDRESS] - 1) & tms34061.xmask); tms34061.regs[TMS34061_XYADDRESS] -= 1 << tms34061.yshift; break; case 0x16: /* X = 0, Y - 1 */ tms34061.regs[TMS34061_XYADDRESS] &= ~tms34061.xmask; tms34061.regs[TMS34061_XYADDRESS] -= 1 << tms34061.yshift; break; case 0x18: /* Y = 0 */ tms34061.regs[TMS34061_XYADDRESS] &= tms34061.xmask; break; case 0x1a: /* X + 1, Y = 0 */ tms34061.regs[TMS34061_XYADDRESS]++; tms34061.regs[TMS34061_XYADDRESS] &= tms34061.xmask; break; case 0x1c: /* X - 1, Y = 0 */ tms34061.regs[TMS34061_XYADDRESS]--; tms34061.regs[TMS34061_XYADDRESS] &= tms34061.xmask; break; case 0x1e: /* X = 0, Y = 0 */ tms34061.regs[TMS34061_XYADDRESS] = 0; break; } } static WRITE8_HANDLER( xypixel_w ) { /* determine the offset, then adjust it */ offs_t pixeloffs = tms34061.regs[TMS34061_XYADDRESS]; if (offset) adjust_xyaddress(offset); /* adjust for the upper bits */ pixeloffs |= (tms34061.regs[TMS34061_XYOFFSET] & 0x0f00) << 8; /* mask to the VRAM size */ pixeloffs &= tms34061.vrammask; /* set the pixel data */ tms34061.vram[pixeloffs] = data; tms34061.latchram[pixeloffs] = tms34061.latchdata; } static READ8_HANDLER( xypixel_r ) { /* determine the offset, then adjust it */ offs_t pixeloffs = tms34061.regs[TMS34061_XYADDRESS]; if (offset) adjust_xyaddress(offset); /* adjust for the upper bits */ pixeloffs |= (tms34061.regs[TMS34061_XYOFFSET] & 0x0f00) << 8; /* mask to the VRAM size */ pixeloffs &= tms34061.vrammask; /* return the result */ return tms34061.vram[pixeloffs]; } /************************************* * * Core writes * *************************************/ void tms34061_w(int col, int row, int func, UINT8 data) { offs_t offs; /* the function code determines what to do */ switch (func) { /* both 0 and 2 map to register access */ case 0: case 2: register_w(col, data); break; /* function 1 maps to XY access; col is the address adjustment */ case 1: xypixel_w(col, data); break; /* function 3 maps to direct access */ case 3: offs = ((row << tms34061.intf.rowshift) | col) & tms34061.vrammask; if (tms34061.regs[TMS34061_CONTROL2] & 0x0040) offs |= (tms34061.regs[TMS34061_CONTROL2] & 3) << 16; if (tms34061.vram[offs] != data || tms34061.latchram[offs] != tms34061.latchdata) { tms34061.vram[offs] = data; tms34061.latchram[offs] = tms34061.latchdata; } break; /* function 4 performs a shift reg transfer to VRAM */ case 4: offs = col << tms34061.intf.rowshift; if (tms34061.regs[TMS34061_CONTROL2] & 0x0040) offs |= (tms34061.regs[TMS34061_CONTROL2] & 3) << 16; offs &= tms34061.vrammask; memcpy(&tms34061.vram[offs], tms34061.shiftreg, (size_t)1 << tms34061.intf.rowshift); memset(&tms34061.latchram[offs], tms34061.latchdata, (size_t)1 << tms34061.intf.rowshift); break; /* function 5 performs a shift reg transfer from VRAM */ case 5: offs = col << tms34061.intf.rowshift; if (tms34061.regs[TMS34061_CONTROL2] & 0x0040) offs |= (tms34061.regs[TMS34061_CONTROL2] & 3) << 16; offs &= tms34061.vrammask; tms34061.shiftreg = &tms34061.vram[offs]; break; /* log anything else */ default: logerror("Unsupported TMS34061 function %d - PC: %04X\n", func, activecpu_get_pc()); break; } } UINT8 tms34061_r(int col, int row, int func) { int result = 0; offs_t offs; /* the function code determines what to do */ switch (func) { /* both 0 and 2 map to register access */ case 0: case 2: result = register_r(col); break; /* function 1 maps to XY access; col is the address adjustment */ case 1: result = xypixel_r(col); break; /* funtion 3 maps to direct access */ case 3: offs = ((row << tms34061.intf.rowshift) | col) & tms34061.vrammask; result = tms34061.vram[offs]; break; /* function 4 performs a shift reg transfer to VRAM */ case 4: offs = col << tms34061.intf.rowshift; if (tms34061.regs[TMS34061_CONTROL2] & 0x0040) offs |= (tms34061.regs[TMS34061_CONTROL2] & 3) << 16; offs &= tms34061.vrammask; memcpy(&tms34061.vram[offs], tms34061.shiftreg, (size_t)1 << tms34061.intf.rowshift); memset(&tms34061.latchram[offs], tms34061.latchdata, (size_t)1 << tms34061.intf.rowshift); break; /* function 5 performs a shift reg transfer from VRAM */ case 5: offs = col << tms34061.intf.rowshift; if (tms34061.regs[TMS34061_CONTROL2] & 0x0040) offs |= (tms34061.regs[TMS34061_CONTROL2] & 3) << 16; offs &= tms34061.vrammask; tms34061.shiftreg = &tms34061.vram[offs]; break; /* log anything else */ default: logerror("Unsupported TMS34061 function %d - PC: %04X\n", func, activecpu_get_pc()); break; } return result; } /************************************* * * Misc functions * *************************************/ READ8_HANDLER( tms34061_latch_r ) { return tms34061.latchdata; } WRITE8_HANDLER( tms34061_latch_w ) { if (VERBOSE) logerror("tms34061_latch = %02X\n", data); tms34061.latchdata = data; } void tms34061_get_display_state(struct tms34061_display *state) { state->blanked = (~tms34061.regs[TMS34061_CONTROL2] >> 13) & 1; state->vram = tms34061.vram; state->latchram = tms34061.latchram; state->regs = tms34061.regs; /* compute the display start */ state->dispstart = (tms34061.regs[TMS34061_DISPSTART] << (tms34061.intf.rowshift - 2)) & tms34061.vrammask; }