mirror of
https://github.com/holub/mame
synced 2025-04-26 02:07:14 +03:00

is either DEVICE_START_OK or DEVICE_START_MISSING_DEPENDENCY. The latter should be returned by a device if there is another device it depends on which hasn't been started yet. Added new flag in the device interface to indicate whether a device has been started. Changed laserdisc interface to explicitly specify the screen and sound devices it should route to. Drivers no longer have to manually call laserdisc_vsync(). Instead, the laserdisc code connects up to the routed screen device and works based on that screen's VBLANK timing. Removed all existing calls to laserdisc_vsync(). Changed laserdisc behavior so that it completes the previous video read and initiates the next read at the end of VBLANK instead of the beginning. This gives player logic time during VBLANK to alter the slider position prior to fetching the next frame. Added new laserdisc callback for vsync begin and changed the update callback to be called at the end of VBLANK. Also added functions to set the slider speed, advance the slider, and directly control the video/ audio squelch. In addition, there is a new status function to get the slider position in general terms. Added parameter to the VBLANK callbacks supported in emu/video.c. Updated all callers to provide a callback value. Fixed bug that would cause watchpoints to trigger if you had a memory window open to the watchpoint address. Further updates to the PR-8210 ROM simulation. Still not quite there but the system is much better understood now. Added layout to the PR-8210 which displays the state of the front-panel LEDs.
500 lines
13 KiB
C
500 lines
13 KiB
C
/**********************************************************************
|
|
|
|
Z80 DMA interface and emulation
|
|
|
|
For datasheet http://www.zilog.com/docs/z80/ps0179.pdf
|
|
|
|
2008/01 couriersud
|
|
|
|
- architecture copied from 8257 DMA
|
|
- significant changes to implementation
|
|
- This is only a minimum implementation to support dkong3 and mario drivers
|
|
- Only memory to memory is tested!
|
|
|
|
TODO:
|
|
- implement missing features
|
|
- implement interrupt support (not used in dkong3 and mario)
|
|
- implement more asserts
|
|
- implement a INPUT_LINE_BUSREQ for Z80. As a workaround,
|
|
HALT is used. This implies burst mode.
|
|
|
|
**********************************************************************/
|
|
|
|
#include "driver.h"
|
|
#include "memconv.h"
|
|
#include "z80dma.h"
|
|
|
|
#define VERBOSE 0
|
|
|
|
#define LOG(x) do { if (VERBOSE) printf x; } while (0)
|
|
|
|
|
|
#define REGNUM(_m, _s) (((_m)<<3) + (_s))
|
|
#define GET_REGNUM(_c, _r) (&(_r) - &(WR0(_c)))
|
|
#define REG(_c, _m, _s) (_c)->regs[REGNUM(_m,_s)]
|
|
#define WR0(_c) REG(_c, 0, 0)
|
|
#define WR1(_c) REG(_c, 1, 0)
|
|
#define WR2(_c) REG(_c, 2, 0)
|
|
#define WR3(_c) REG(_c, 3, 0)
|
|
#define WR4(_c) REG(_c, 4, 0)
|
|
#define WR5(_c) REG(_c, 5, 0)
|
|
#define WR6(_c) REG(_c, 6, 0)
|
|
|
|
#define PORTA_ADDRESS_L(_c) REG(_c,0,1)
|
|
#define PORTA_ADDRESS_H(_c) REG(_c,0,2)
|
|
|
|
#define BLOCKLEN_L(_c) REG(_c,0,3)
|
|
#define BLOCKLEN_H(_c) REG(_c,0,4)
|
|
|
|
#define PORTA_TIMING(_c) REG(_c,1,1)
|
|
#define PORTB_TIMING(_c) REG(_c,2,1)
|
|
|
|
#define MASK_BYTE(_c) REG(_c,3,1)
|
|
#define MATCH_BYTE(_c) REG(_c,3,2)
|
|
|
|
#define PORTB_ADDRESS_L(_c) REG(_c,4,1)
|
|
#define PORTB_ADDRESS_H(_c) REG(_c,4,2)
|
|
#define INTERRUPT_CTRL(_c) REG(_c,4,3)
|
|
#define INTERRUPT_VECTOR(_c) REG(_c,4,4)
|
|
#define PULSE_CTRL(_c) REG(_c,4,5)
|
|
|
|
#define READ_MASK(_c) REG(_c,6,1)
|
|
|
|
|
|
#define PORTA_ADDRESS(_c) ((PORTA_ADDRESS_H(_c)<<8) | PORTA_ADDRESS_L(_c))
|
|
#define PORTB_ADDRESS(_c) ((PORTB_ADDRESS_H(_c)<<8) | PORTB_ADDRESS_L(_c))
|
|
#define BLOCKLEN(_c) ((BLOCKLEN_H(_c)<<8) | BLOCKLEN_L(_c))
|
|
|
|
#define PORTA_STEP(_c) (((WR1(_c) >> 4) & 0x03)*2-1)
|
|
#define PORTB_STEP(_c) (((WR2(_c) >> 4) & 0x03)*2-1)
|
|
#define PORTA_FIXED(_c) (((WR1(_c) >> 4) & 0x02) == 0x02)
|
|
#define PORTB_FIXED(_c) (((WR2(_c) >> 4) & 0x02) == 0x02)
|
|
#define PORTA_MEMORY(_c) (((WR1(_c) >> 3) & 0x01) == 0x00)
|
|
#define PORTB_MEMORY(_c) (((WR2(_c) >> 3) & 0x01) == 0x00)
|
|
|
|
#define PORTA_CYCLE_LEN(_c) (4-(PORTA_TIMING(_c) & 0x03))
|
|
#define PORTB_CYCLE_LEN(_c) (4-(PORTB_TIMING(_c) & 0x03))
|
|
|
|
#define PORTA_IS_SOURCE(_c) ((WR0(_c) >> 2) & 0x01)
|
|
#define PORTB_IS_SOURCE(_c) (!PORTA_IS_SOURCE(_c))
|
|
#define TRANSFER_MODE(_c) (WR0(_c) & 0x03)
|
|
|
|
#define TM_TRANSFER (0x01)
|
|
#define TM_SEARCH (0x02)
|
|
#define TM_SEARCH_TRANSFER (0x03)
|
|
|
|
#define READY_ACTIVE_HIGH(_c) ((WR5(_c)>>3) & 0x01)
|
|
|
|
typedef struct _z80dma_t z80dma_t;
|
|
struct _z80dma_t
|
|
{
|
|
const z80dma_interface *intf;
|
|
emu_timer *timer;
|
|
|
|
UINT16 regs[REGNUM(6,1)+1];
|
|
UINT8 num_follow;
|
|
UINT8 cur_follow;
|
|
UINT8 regs_follow[4];
|
|
UINT8 status;
|
|
UINT8 dma_enabled;
|
|
|
|
UINT16 addressA;
|
|
UINT16 addressB;
|
|
UINT16 count;
|
|
|
|
UINT8 rdy;
|
|
|
|
UINT8 is_read;
|
|
UINT8 cur_cycle;
|
|
UINT8 latch;
|
|
};
|
|
|
|
static TIMER_CALLBACK( z80dma_timerproc );
|
|
static void z80dma_update_status(const device_config *device);
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
INLINE z80dma_t *get_safe_token(const device_config *device) {
|
|
assert( device != NULL );
|
|
assert( device->token != NULL );
|
|
assert( device->type == Z80DMA );
|
|
return ( z80dma_t * ) device->token;
|
|
}
|
|
|
|
static void z80dma_do_read(const device_config *device)
|
|
{
|
|
z80dma_t *cntx = get_safe_token(device);
|
|
UINT8 mode;
|
|
|
|
mode = TRANSFER_MODE(cntx);
|
|
switch(mode) {
|
|
case TM_TRANSFER:
|
|
if (PORTA_IS_SOURCE(cntx))
|
|
{
|
|
if (PORTA_MEMORY(cntx))
|
|
cntx->latch = cntx->intf->memory_read(device, cntx->addressA);
|
|
else
|
|
cntx->latch = cntx->intf->portA_read(device, cntx->addressA);
|
|
cntx->addressA += PORTA_STEP(cntx);
|
|
}
|
|
else
|
|
{
|
|
if (PORTB_MEMORY(cntx))
|
|
cntx->latch = cntx->intf->memory_read(device, cntx->addressB);
|
|
else
|
|
cntx->latch = cntx->intf->portB_read(device, cntx->addressB);
|
|
cntx->addressB += PORTB_STEP(cntx);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fatalerror("z80dma_do_operation: invalid mode %d!\n", mode);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int z80dma_do_write(const device_config *device)
|
|
{
|
|
z80dma_t *cntx = get_safe_token(device);
|
|
int done;
|
|
UINT8 mode;
|
|
|
|
mode = TRANSFER_MODE(cntx);
|
|
if (cntx->count == 0x0000)
|
|
{
|
|
//FIXME: Any signal here
|
|
}
|
|
switch(mode) {
|
|
case TM_TRANSFER:
|
|
if (PORTA_IS_SOURCE(cntx))
|
|
{
|
|
if (PORTB_MEMORY(cntx))
|
|
cntx->intf->memory_write(device, cntx->addressB, cntx->latch);
|
|
else
|
|
cntx->intf->portB_write(device, cntx->addressB, cntx->latch);
|
|
cntx->addressB += PORTB_STEP(cntx);
|
|
}
|
|
else
|
|
{
|
|
if (PORTA_MEMORY(cntx))
|
|
cntx->intf->memory_write(device, cntx->addressA, cntx->latch);
|
|
else
|
|
cntx->intf->portB_write(device, cntx->addressA, cntx->latch);
|
|
cntx->addressA += PORTA_STEP(cntx);
|
|
}
|
|
cntx->count--;
|
|
done = (cntx->count == 0xFFFF);
|
|
break;
|
|
|
|
default:
|
|
fatalerror("z80dma_do_operation: invalid mode %d!\n", mode);
|
|
break;
|
|
}
|
|
if (done)
|
|
{
|
|
//FIXME: interrupt ?
|
|
}
|
|
return done;
|
|
}
|
|
|
|
static TIMER_CALLBACK( z80dma_timerproc )
|
|
{
|
|
const device_config *device = ptr;
|
|
z80dma_t *cntx = get_safe_token(device);
|
|
int done;
|
|
|
|
if (--cntx->cur_cycle)
|
|
{
|
|
return;
|
|
}
|
|
if (cntx->is_read)
|
|
{
|
|
z80dma_do_read(device);
|
|
done = 0;
|
|
cntx->is_read = 0;
|
|
cntx->cur_cycle = (PORTA_IS_SOURCE(cntx) ? PORTA_CYCLE_LEN(cntx) : PORTB_CYCLE_LEN(cntx));
|
|
}
|
|
else
|
|
{
|
|
done = z80dma_do_write(device);
|
|
cntx->is_read = 1;
|
|
cntx->cur_cycle = (PORTB_IS_SOURCE(cntx) ? PORTA_CYCLE_LEN(cntx) : PORTB_CYCLE_LEN(cntx));
|
|
}
|
|
if (done)
|
|
{
|
|
cntx->dma_enabled = 0; //FIXME: Correct?
|
|
z80dma_update_status(device);
|
|
}
|
|
}
|
|
|
|
static void z80dma_update_status(const device_config *device)
|
|
{
|
|
z80dma_t *z80dma = get_safe_token(device);
|
|
UINT16 pending_transfer;
|
|
attotime next;
|
|
|
|
/* no transfer is active right now; is there a transfer pending right now? */
|
|
pending_transfer = z80dma->rdy & z80dma->dma_enabled;
|
|
|
|
if (pending_transfer)
|
|
{
|
|
z80dma->is_read = 1;
|
|
z80dma->cur_cycle = (PORTA_IS_SOURCE(z80dma) ? PORTA_CYCLE_LEN(z80dma) : PORTB_CYCLE_LEN(z80dma));
|
|
next = ATTOTIME_IN_HZ(z80dma->intf->clockhz);
|
|
timer_adjust_periodic(z80dma->timer,
|
|
attotime_zero,
|
|
0,
|
|
/* 1 byte transferred in 4 clock cycles */
|
|
next);
|
|
}
|
|
else
|
|
{
|
|
/* no transfers active right now */
|
|
timer_reset(z80dma->timer, attotime_never);
|
|
}
|
|
|
|
/* set the halt line */
|
|
if (z80dma->intf && z80dma->intf->cpunum >= 0)
|
|
{
|
|
//FIXME: Synchronization is done by BUSREQ!
|
|
cpunum_set_input_line(device->machine, z80dma->intf->cpunum, INPUT_LINE_HALT,
|
|
pending_transfer ? ASSERT_LINE : CLEAR_LINE);
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
READ8_DEVICE_HANDLER( z80dma_r )
|
|
{
|
|
fatalerror("z80dma_read: not implemented");
|
|
return 0;
|
|
}
|
|
|
|
|
|
WRITE8_DEVICE_HANDLER( z80dma_w )
|
|
{
|
|
z80dma_t *cntx = get_safe_token(device);
|
|
|
|
if (cntx->num_follow == 0)
|
|
{
|
|
if ((data & 0x87) == 0) // WR2
|
|
{
|
|
WR2(cntx) = data;
|
|
if (data & 0x40)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, PORTB_TIMING(cntx));
|
|
}
|
|
else if ((data & 0x87) == 0x04) // WR1
|
|
{
|
|
WR1(cntx) = data;
|
|
if (data & 0x40)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, PORTA_TIMING(cntx));
|
|
}
|
|
else if ((data & 0x80) == 0) // WR0
|
|
{
|
|
WR0(cntx) = data;
|
|
if (data & 0x08)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, PORTA_ADDRESS_L(cntx));
|
|
if (data & 0x10)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, PORTA_ADDRESS_H(cntx));
|
|
if (data & 0x20)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, BLOCKLEN_L(cntx));
|
|
if (data & 0x40)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, BLOCKLEN_H(cntx));
|
|
}
|
|
else if ((data & 0x83) == 0x80) // WR3
|
|
{
|
|
WR3(cntx) = data;
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, MASK_BYTE(cntx));
|
|
if (data & 0x10)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, MATCH_BYTE(cntx));
|
|
}
|
|
else if ((data & 0x83) == 0x81) // WR4
|
|
{
|
|
WR4(cntx) = data;
|
|
if (data & 0x04)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, PORTB_ADDRESS_L(cntx));
|
|
if (data & 0x08)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, PORTB_ADDRESS_H(cntx));
|
|
if (data & 0x10)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, INTERRUPT_CTRL(cntx));
|
|
}
|
|
else if ((data & 0xC7) == 0x82) // WR5
|
|
{
|
|
WR5(cntx) = data;
|
|
}
|
|
else if ((data & 0x83) == 0x83) // WR6
|
|
{
|
|
WR6(cntx) = data;
|
|
switch (data)
|
|
{
|
|
case 0x88: /* Reinitialize status byte */
|
|
case 0xA3: /* Reset and disable interrupts */
|
|
case 0xA7: /* Initiate read sequence */
|
|
case 0xAB: /* Enable interrupts */
|
|
case 0xAF: /* Disable interrupts */
|
|
case 0xB3: /* Force ready */
|
|
case 0xB7: /* Enable after rti */
|
|
case 0xBF: /* Read status byte */
|
|
case 0xD3: /* Continue */
|
|
fatalerror("Unimplemented WR6 command %02x", data);
|
|
break;
|
|
case 0xC3: /* Reset */
|
|
LOG(("Reset\n"));
|
|
break;
|
|
case 0xCF: /* Load */
|
|
cntx->addressA = PORTA_ADDRESS(cntx);
|
|
cntx->addressB = PORTB_ADDRESS(cntx);
|
|
cntx->count = BLOCKLEN(cntx);
|
|
LOG(("Load A: %x B: %x N: %x\n", cntx->addressA, cntx->addressB, cntx->count));
|
|
break;
|
|
case 0x83: /* Disable dma */
|
|
cntx->dma_enabled = 0;
|
|
z80dma_rdy_w(device, 0, cntx->rdy);
|
|
break;
|
|
case 0x87: /* Enable dma */
|
|
cntx->dma_enabled = 1;
|
|
z80dma_rdy_w(device, 0, cntx->rdy);
|
|
break;
|
|
case 0xBB:
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, READ_MASK(cntx));
|
|
break;
|
|
default:
|
|
fatalerror("Unknown WR6 command %02x", data);
|
|
}
|
|
}
|
|
else
|
|
fatalerror("Unknown base register %02x", data);
|
|
cntx->cur_follow = 0;
|
|
}
|
|
else
|
|
{
|
|
int nreg = cntx->regs_follow[cntx->cur_follow];
|
|
cntx->regs[nreg] = data;
|
|
cntx->cur_follow++;
|
|
if (cntx->cur_follow>=cntx->num_follow)
|
|
cntx->num_follow = 0;
|
|
if (nreg == REGNUM(4,3))
|
|
{
|
|
cntx->num_follow=0;
|
|
if (data & 0x08)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, PULSE_CTRL(cntx));
|
|
if (data & 0x10)
|
|
cntx->regs_follow[cntx->num_follow++] = GET_REGNUM(cntx, INTERRUPT_VECTOR(cntx));
|
|
cntx->cur_follow = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static TIMER_CALLBACK( z80dma_rdy_write_callback )
|
|
{
|
|
const device_config *device = ptr;
|
|
int state = param & 0x01;
|
|
z80dma_t *cntx = get_safe_token(device);
|
|
|
|
/* normalize state */
|
|
cntx->rdy = 1 ^ state ^ READY_ACTIVE_HIGH(cntx);
|
|
cntx->status = (cntx->status & 0xFD) | (cntx->rdy<<1);
|
|
|
|
z80dma_update_status(device);
|
|
}
|
|
|
|
|
|
WRITE8_DEVICE_HANDLER( z80dma_rdy_w)
|
|
{
|
|
z80dma_t *z80dma = get_safe_token(device);
|
|
int param;
|
|
|
|
assert( offset == 0);
|
|
|
|
param = (data ? 1 : 0);
|
|
LOG(("RDY: %d Active High: %d\n", data, READY_ACTIVE_HIGH(z80dma)));
|
|
timer_call_after_resynch((void *) device, param, z80dma_rdy_write_callback);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
/* device interface */
|
|
|
|
static DEVICE_START( z80dma )
|
|
{
|
|
z80dma_t *z80dma = get_safe_token(device);
|
|
char unique_tag[30];
|
|
|
|
/* validate arguments */
|
|
assert(device != NULL);
|
|
assert(device->tag != NULL);
|
|
assert(strlen(device->tag) < 20);
|
|
|
|
z80dma->intf = device->static_config;
|
|
|
|
z80dma->timer = timer_alloc(z80dma_timerproc, (void *) device);
|
|
|
|
state_save_combine_module_and_tag(unique_tag, "z80dma", device->tag);
|
|
|
|
state_save_register_item_array(unique_tag, 0, z80dma->regs);
|
|
state_save_register_item_array(unique_tag, 0, z80dma->regs_follow);
|
|
|
|
state_save_register_item(unique_tag, 0, z80dma->num_follow);
|
|
state_save_register_item(unique_tag, 0, z80dma->cur_follow);
|
|
state_save_register_item(unique_tag, 0, z80dma->status);
|
|
state_save_register_item(unique_tag, 0, z80dma->dma_enabled);
|
|
|
|
state_save_register_item(unique_tag, 0, z80dma->addressA);
|
|
state_save_register_item(unique_tag, 0, z80dma->addressB);
|
|
state_save_register_item(unique_tag, 0, z80dma->count);
|
|
state_save_register_item(unique_tag, 0, z80dma->rdy);
|
|
state_save_register_item(unique_tag, 0, z80dma->is_read);
|
|
state_save_register_item(unique_tag, 0, z80dma->cur_cycle);
|
|
state_save_register_item(unique_tag, 0, z80dma->latch);
|
|
|
|
return DEVICE_START_OK;
|
|
}
|
|
|
|
|
|
static DEVICE_RESET( z80dma )
|
|
{
|
|
z80dma_t *z80dma = get_safe_token(device);
|
|
|
|
z80dma->status = 0;
|
|
z80dma->rdy = 0;
|
|
z80dma->num_follow = 0;
|
|
z80dma->dma_enabled = 0;
|
|
z80dma_update_status(device);
|
|
}
|
|
|
|
|
|
static DEVICE_SET_INFO( z80dma )
|
|
{
|
|
switch (state)
|
|
{
|
|
/* no parameters to set */
|
|
}
|
|
}
|
|
|
|
|
|
DEVICE_GET_INFO( z80dma )
|
|
{
|
|
switch (state)
|
|
{
|
|
/* --- the following bits of info are returned as 64-bit signed integers --- */
|
|
case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(z80dma_t); break;
|
|
case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = 0; break;
|
|
case DEVINFO_INT_CLASS: info->i = DEVICE_CLASS_PERIPHERAL; break;
|
|
|
|
/* --- the following bits of info are returned as pointers to data or functions --- */
|
|
case DEVINFO_FCT_SET_INFO: info->set_info = DEVICE_SET_INFO_NAME(z80dma); break;
|
|
case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(z80dma);break;
|
|
case DEVINFO_FCT_STOP: /* Nothing */ break;
|
|
case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(z80dma);break;
|
|
|
|
/* --- the following bits of info are returned as NULL-terminated strings --- */
|
|
case DEVINFO_STR_NAME: info->s = "Z80DMA"; break;
|
|
case DEVINFO_STR_FAMILY: info->s = "DMA controllers"; break;
|
|
case DEVINFO_STR_VERSION: info->s = "1.0"; break;
|
|
case DEVINFO_STR_SOURCE_FILE: info->s = __FILE__; break;
|
|
case DEVINFO_STR_CREDITS: info->s = "Copyright Nicola Salmoria and the MAME Team"; break;
|
|
}
|
|
}
|
|
|