/* NEC uPD71071 DMA Controller Used on the Fujitsu FM-Towns Register description: 0x00: Initialise (Write-only) - bit 0: Reset - bit 1: 16-bit data bus 0x01: Channel Register On read: - bits 0-3: Selected channel - bit 4: Only base registers may be read or written On write: - bits 0-1: Select channel for programming count, address, and mode registers - bit 2: Only base registers can be read or written to 0x02: 0x03: Count Register (16-bit) DMA Transfer counter 0x04: 0x05: 0x06: 0x07: Address Register (32-bit) Self-explanatory, I hope. :) NOTE: Datasheet clearly shows this as 24-bit, with register 7 unused. But the FM-Towns definitely uses reg 7 as bits 24-31. 0x08: 0x09: Device Control register (16-bit) bit 0: Enable memory-to-memory (MTM) transfers bit 1: Enable fixed address for channel 0 only (MTM only) bit 2: Disable DMA operation (stops HLDRQ signal to the CPU) bit 3: Use compressed timing bit 4: Rotational Priority bit 5: Extended Writing bit 6: DMARQ active level (1=active low) bit 7: DMAAK active level (1=active high) bit 8: Bus mode (0=bus release, 1=bus hold) bit 9: Wait Enable during Verify 0x0a: Mode Control register bit 0: Transfer size (1=16-bit, 0=8-bit, 16-bit data bus size only) bit 2-3: Transfer direction (ignored for MTM transfers) 00 = Verify 01 = I/O to memory 10 = memory to I/O 11 = invalid bit 4: Enable auto-initialise bit 5: Address direction (0=increment, 1=decrement, affects only current Address reg) bit 6-7: Transfer mode (ignored for MTM transfers) 00 = Demand 01 = Single 10 = Block 11 = Cascade 0x0b: Status register bit 0-3: Terminal count (per channel) bit 4-7: DMA request present (external hardware DMA only) 0x0c: 0x0d: Temporary register (16-bit, read-only) Stores the last data transferred in an MTM transfer 0x0e: Request register bit 0-3: Software DMA request (1=set) bit 0 only in MTM transfers 0x0f: Mask register bit 0-3: DMARQ mask bits 1 and 0 only in MTM transfers */ #include "emu.h" #include "machine/upd71071.h" struct upd71071_reg { UINT8 initialise; UINT8 channel; UINT16 count_current[4]; UINT16 count_base[4]; UINT32 address_current[4]; UINT32 address_base[4]; UINT16 device_control; UINT8 mode_control[4]; UINT8 status; UINT8 temp_l; UINT8 temp_h; UINT8 request; UINT8 mask; }; struct upd71071_t { struct upd71071_reg reg; int selected_channel; int buswidth; int dmarq[4]; emu_timer* timer[4]; int in_progress[4]; int transfer_size[4]; int base; const upd71071_intf* intf; devcb_resolved_write_line m_out_hreq_func; devcb_resolved_write_line m_out_eop_func; devcb_resolved_read16 m_dma_read[4]; devcb_resolved_write16 m_dma_write[4]; devcb_resolved_write_line m_out_dack_func[4]; int m_hreq; int m_eop; }; INLINE upd71071_t *get_safe_token(device_t *device) { assert(device != NULL); assert(device->type() == UPD71071); return (upd71071_t*)downcast(device)->token(); } static TIMER_CALLBACK(dma_transfer_timer) { // single byte or word transfer device_t* device = (device_t*)ptr; upd71071_t* dmac = get_safe_token(device); address_space& space = device->machine().device(dmac->intf->cputag)->memory().space(AS_PROGRAM); int channel = param; UINT16 data = 0; // data to transfer switch(dmac->reg.mode_control[channel] & 0x0c) { case 0x00: // Verify break; case 0x04: // I/O -> memory if(!dmac->m_dma_read[channel].isnull()) data = dmac->m_dma_read[channel](0); space.write_byte(dmac->reg.address_current[channel],data & 0xff); if(dmac->reg.mode_control[channel] & 0x20) // Address direction dmac->reg.address_current[channel]--; else dmac->reg.address_current[channel]++; if(dmac->reg.count_current[channel] == 0) { if(dmac->reg.mode_control[channel] & 0x10) // auto-initialise { dmac->reg.address_current[channel] = dmac->reg.address_base[channel]; dmac->reg.count_current[channel] = dmac->reg.count_base[channel]; } // TODO: send terminal count set_eop(device,ASSERT_LINE); } else dmac->reg.count_current[channel]--; break; case 0x08: // memory -> I/O data = space.read_byte(dmac->reg.address_current[channel]); if(!dmac->m_dma_write[channel].isnull()) dmac->m_dma_write[channel](0,data); if(dmac->reg.mode_control[channel] & 0x20) // Address direction dmac->reg.address_current[channel]--; else dmac->reg.address_current[channel]++; if(dmac->reg.count_current[channel] == 0) { if(dmac->reg.mode_control[channel] & 0x10) // auto-initialise { dmac->reg.address_current[channel] = dmac->reg.address_base[channel]; dmac->reg.count_current[channel] = dmac->reg.count_base[channel]; } // TODO: send terminal count set_eop(device,ASSERT_LINE); } else dmac->reg.count_current[channel]--; break; case 0x0c: // Invalid break; } } static void upd71071_soft_reset(device_t* device) { upd71071_t* dmac = get_safe_token(device); int x; // Does not change base/current address, count, or buswidth dmac->selected_channel = 0; dmac->base = 0; for(x=0;x<4;x++) dmac->reg.mode_control[x] = 0; dmac->reg.device_control = 0; dmac->reg.temp_h = 0; dmac->reg.temp_l = 0; dmac->reg.mask = 0x0f; // mask all channels dmac->reg.status &= ~0x0f; // clears bits 0-3 only dmac->reg.request = 0; } int upd71071_dmarq(device_t* device, int state,int channel) { upd71071_t* dmac = get_safe_token(device); if(state != 0) { if(dmac->reg.device_control & 0x0004) return 2; if(dmac->reg.mask & (1 << channel)) // is channel masked? return 1; dmac->dmarq[channel] = 1; // DMARQ line is set dmac->reg.status |= (0x10 << channel); // start transfer switch(dmac->reg.mode_control[channel] & 0xc0) { case 0x00: // Demand // TODO set_eop(device,CLEAR_LINE); dmac->timer[channel]->adjust(attotime::from_hz(dmac->intf->clock),channel); break; case 0x40: // Single dmac->timer[channel]->adjust(attotime::from_hz(dmac->intf->clock),channel); break; case 0x80: // Block // TODO break; case 0xc0: // Cascade // TODO break; } } else { dmac->dmarq[channel] = 0; // clear DMARQ line dmac->reg.status &= ~(0x10 << channel); dmac->reg.status |= (0x01 << channel); // END or TC } return 0; } static DEVICE_START(upd71071) { upd71071_t* dmac = get_safe_token(device); int x; dmac->intf = (const upd71071_intf*)device->static_config(); dmac->m_out_hreq_func.resolve(dmac->intf->m_out_hreq_cb, *device); dmac->m_out_eop_func.resolve(dmac->intf->m_out_eop_cb, *device); for(x=0;x<4;x++) { dmac->timer[x] = device->machine().scheduler().timer_alloc(FUNC(dma_transfer_timer), (void*)device); dmac->m_dma_read[x].resolve(dmac->intf->m_dma_read[x], *device); dmac->m_dma_write[x].resolve(dmac->intf->m_dma_write[x], *device); dmac->m_out_dack_func[x].resolve(dmac->intf->m_out_dack_cb[x], *device); } dmac->selected_channel = 0; } static READ8_DEVICE_HANDLER(upd71071_read) { upd71071_t* dmac = get_safe_token(device); UINT8 ret = 0; logerror("DMA: read from register %02x\n",offset); switch(offset) { case 0x01: // Channel ret = (1 << dmac->selected_channel); if(dmac->base != 0) ret |= 0x10; break; case 0x02: // Count (low) if(dmac->base != 0) ret = dmac->reg.count_base[dmac->selected_channel] & 0xff; else ret = dmac->reg.count_current[dmac->selected_channel] & 0xff; break; case 0x03: // Count (high) if(dmac->base != 0) ret = (dmac->reg.count_base[dmac->selected_channel] >> 8) & 0xff; else ret = (dmac->reg.count_current[dmac->selected_channel] >> 8) & 0xff; break; case 0x04: // Address (low) if(dmac->base != 0) ret = dmac->reg.address_base[dmac->selected_channel] & 0xff; else ret = dmac->reg.address_current[dmac->selected_channel] & 0xff; break; case 0x05: // Address (mid) if(dmac->base != 0) ret = (dmac->reg.address_base[dmac->selected_channel] >> 8) & 0xff; else ret = (dmac->reg.address_current[dmac->selected_channel] >> 8) & 0xff; break; case 0x06: // Address (high) if(dmac->base != 0) ret = (dmac->reg.address_base[dmac->selected_channel] >> 16) & 0xff; else ret = (dmac->reg.address_current[dmac->selected_channel] >> 16) & 0xff; break; case 0x07: // Address (highest) if(dmac->base != 0) ret = (dmac->reg.address_base[dmac->selected_channel] >> 24) & 0xff; else ret = (dmac->reg.address_current[dmac->selected_channel] >> 24) & 0xff; break; case 0x08: // Device control (low) ret = dmac->reg.device_control & 0xff; break; case 0x09: // Device control (high) ret = (dmac->reg.device_control >> 8) & 0xff; break; case 0x0a: // Mode control ret = dmac->reg.mode_control[dmac->selected_channel]; break; case 0x0b: // Status ret = dmac->reg.status; dmac->reg.status &= ~0x0f; // resets END/TC? break; case 0x0c: // Temporary (low) ret = dmac->reg.temp_h; break; case 0x0d: // Temporary (high) ret = dmac->reg.temp_l; break; case 0x0e: // Request ret = dmac->reg.request; break; case 0x0f: // Mask ret = dmac->reg.mask; break; } return ret; } static WRITE8_DEVICE_HANDLER(upd71071_write) { upd71071_t* dmac = get_safe_token(device); switch(offset) { case 0x00: // Initialise // TODO: reset (bit 0) dmac->buswidth = data & 0x02; if(data & 0x01) upd71071_soft_reset(device); logerror("DMA: Initialise [%02x]\n",data); break; case 0x01: // Channel dmac->selected_channel = data & 0x03; dmac->base = data & 0x04; logerror("DMA: Channel selected [%02x]\n",data); break; case 0x02: // Count (low) dmac->reg.count_base[dmac->selected_channel] = (dmac->reg.count_base[dmac->selected_channel] & 0xff00) | data; if(dmac->base == 0) dmac->reg.count_current[dmac->selected_channel] = (dmac->reg.count_current[dmac->selected_channel] & 0xff00) | data; logerror("DMA: Channel %i Counter set [%04x]\n",dmac->selected_channel,dmac->reg.count_base[dmac->selected_channel]); break; case 0x03: // Count (high) dmac->reg.count_base[dmac->selected_channel] = (dmac->reg.count_base[dmac->selected_channel] & 0x00ff) | (data << 8); if(dmac->base == 0) dmac->reg.count_current[dmac->selected_channel] = (dmac->reg.count_current[dmac->selected_channel] & 0x00ff) | (data << 8); logerror("DMA: Channel %i Counter set [%04x]\n",dmac->selected_channel,dmac->reg.count_base[dmac->selected_channel]); break; case 0x04: // Address (low) dmac->reg.address_base[dmac->selected_channel] = (dmac->reg.address_base[dmac->selected_channel] & 0xffffff00) | data; if(dmac->base == 0) dmac->reg.address_current[dmac->selected_channel] = (dmac->reg.address_current[dmac->selected_channel] & 0xffffff00) | data; logerror("DMA: Channel %i Address set [%08x]\n",dmac->selected_channel,dmac->reg.address_base[dmac->selected_channel]); break; case 0x05: // Address (mid) dmac->reg.address_base[dmac->selected_channel] = (dmac->reg.address_base[dmac->selected_channel] & 0xffff00ff) | (data << 8); if(dmac->base == 0) dmac->reg.address_current[dmac->selected_channel] = (dmac->reg.address_current[dmac->selected_channel] & 0xffff00ff) | (data << 8); logerror("DMA: Channel %i Address set [%08x]\n",dmac->selected_channel,dmac->reg.address_base[dmac->selected_channel]); break; case 0x06: // Address (high) dmac->reg.address_base[dmac->selected_channel] = (dmac->reg.address_base[dmac->selected_channel] & 0xff00ffff) | (data << 16); if(dmac->base == 0) dmac->reg.address_current[dmac->selected_channel] = (dmac->reg.address_current[dmac->selected_channel] & 0xff00ffff) | (data << 16); logerror("DMA: Channel %i Address set [%08x]\n",dmac->selected_channel,dmac->reg.address_base[dmac->selected_channel]); break; case 0x07: // Address (highest) dmac->reg.address_base[dmac->selected_channel] = (dmac->reg.address_base[dmac->selected_channel] & 0x00ffffff) | (data << 24); if(dmac->base == 0) dmac->reg.address_current[dmac->selected_channel] = (dmac->reg.address_current[dmac->selected_channel] & 0x00ffffff) | (data << 24); logerror("DMA: Channel %i Address set [%08x]\n",dmac->selected_channel,dmac->reg.address_base[dmac->selected_channel]); break; case 0x08: // Device control (low) dmac->reg.device_control = (dmac->reg.device_control & 0xff00) | data; logerror("DMA: Device control set [%04x]\n",dmac->reg.device_control); break; case 0x09: // Device control (high) dmac->reg.device_control = (dmac->reg.device_control & 0x00ff) | (data << 8); logerror("DMA: Device control set [%04x]\n",dmac->reg.device_control); break; case 0x0a: // Mode control dmac->reg.mode_control[dmac->selected_channel] = data; logerror("DMA: Channel %i Mode control set [%02x]\n",dmac->selected_channel,dmac->reg.mode_control[dmac->selected_channel]); break; case 0x0e: // Request dmac->reg.request = data; logerror("DMA: Request set [%02x]\n",data); break; case 0x0f: // Mask dmac->reg.mask = data; logerror("DMA: Mask set [%02x]\n",data); break; } } READ8_DEVICE_HANDLER(upd71071_r) { return upd71071_read(device,space,offset,mem_mask); } WRITE8_DEVICE_HANDLER(upd71071_w) { upd71071_write(device,space,offset,data,mem_mask); } const device_type UPD71071 = &device_creator; upd71071_device::upd71071_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : device_t(mconfig, UPD71071, "NEC uPD71071", tag, owner, clock) { m_token = global_alloc_clear(upd71071_t); } //------------------------------------------------- // device_config_complete - perform any // operations now that the configuration is // complete //------------------------------------------------- void upd71071_device::device_config_complete() { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void upd71071_device::device_start() { DEVICE_START_NAME( upd71071 )(this); } void set_hreq( device_t *device, int state) { upd71071_t* dmac = get_safe_token(device); if (dmac->m_hreq != state) { dmac->m_out_hreq_func(state); dmac->m_hreq = state; } } void set_eop( device_t *device, int state) { upd71071_t* dmac = get_safe_token(device); if (dmac->m_eop != state) { dmac->m_out_eop_func(state); dmac->m_eop = state; } }