// license:LGPL-2.1+
// copyright-holders:Angelo Salese, R. Belmont, Juergen Buchmueller
/******************************************************************************
*
* Acorn Archimedes custom chips (IOC, MEMC, VIDC)
*
* Memory map (from http://b-em.bbcmicro.com/arculator/archdocs.txt)
*
* 0000000 - 1FFFFFF - logical RAM (32 meg)
* 2000000 - 2FFFFFF - physical RAM (supervisor only - max 16MB - requires quad MEMCs)
* 3000000 - 33FFFFF - IOC (IO controllers - supervisor only)
* 3310000 - FDC - WD1772
* 33A0000 - Econet - 6854
* 33B0000 - Serial - 6551
* 3240000 - 33FFFFF - internal expansion cards
* 32D0000 - hard disc controller (not IDE) - HD63463
* 3350010 - printer
* 3350018 - latch A
* 3350040 - latch B
* 3270000 - external expansion cards
*
* 3400000 - 3FFFFFF - ROM (read - 12 meg - Arthur and RiscOS 2 512k, RiscOS 3 2MB)
* 3400000 - 37FFFFF - Low ROM (4 meg, I think this is expansion ROMs)
* 3800000 - 3FFFFFF - High ROM (main OS ROM)
*
* 3400000 - 35FFFFF - VICD10 (write - supervisor only)
* 3600000 - 3FFFFFF - MEMC (write - supervisor only)
*
*****************************************************************************/
#include "emu.h"
#include "includes/archimds.h"
#include "cpu/arm/arm.h"
#include "debugger.h"
static const int page_sizes[4] = { 4096, 8192, 16384, 32768 };
#define IOC_LOG 0
#define CRTC_LOG 0
/* TODO: fix pending irqs */
void archimedes_state::archimedes_request_irq_a(int mask)
{
m_ioc_regs[IRQ_STATUS_A] |= mask;
if ((m_ioc_regs[IRQ_STATUS_A] & m_ioc_regs[IRQ_MASK_A]) || (m_ioc_regs[IRQ_STATUS_B] & m_ioc_regs[IRQ_MASK_B]))
m_maincpu->set_input_line(ARM_IRQ_LINE, ASSERT_LINE);
else
m_maincpu->set_input_line(ARM_IRQ_LINE, CLEAR_LINE);
}
void archimedes_state::archimedes_request_irq_b(int mask)
{
m_ioc_regs[IRQ_STATUS_B] |= mask;
if ((m_ioc_regs[IRQ_STATUS_A] & m_ioc_regs[IRQ_MASK_A]) || (m_ioc_regs[IRQ_STATUS_B] & m_ioc_regs[IRQ_MASK_B]))
m_maincpu->set_input_line(ARM_IRQ_LINE, ASSERT_LINE);
else
m_maincpu->set_input_line(ARM_IRQ_LINE, CLEAR_LINE);
}
void archimedes_state::archimedes_request_fiq(int mask)
{
m_ioc_regs[FIQ_STATUS] |= mask;
//printf("STATUS:%02x IRQ:%02x MASK:%02x\n",m_ioc_regs[FIQ_STATUS],mask,m_ioc_regs[FIQ_MASK]);
if (m_ioc_regs[FIQ_STATUS] & m_ioc_regs[FIQ_MASK])
{
m_maincpu->pulse_input_line(ARM_FIRQ_LINE, m_maincpu->minimum_quantum_time());
//m_maincpu->set_input_line(ARM_FIRQ_LINE, CLEAR_LINE);
//m_maincpu->set_input_line(ARM_FIRQ_LINE, ASSERT_LINE);
}
}
void archimedes_state::archimedes_clear_irq_a(int mask)
{
m_ioc_regs[IRQ_STATUS_A] &= ~mask;
archimedes_request_irq_a(0);
}
void archimedes_state::archimedes_clear_irq_b(int mask)
{
m_ioc_regs[IRQ_STATUS_B] &= ~mask;
archimedes_request_irq_b(0);
}
void archimedes_state::archimedes_clear_fiq(int mask)
{
m_ioc_regs[FIQ_STATUS] &= ~mask;
//archimedes_request_fiq(0);
}
void archimedes_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
case TIMER_IOC: ioc_timer(param); break;
}
}
WRITE_LINE_MEMBER( archimedes_state::vblank_irq )
{
if (state)
{
archimedes_request_irq_a(ARCHIMEDES_IRQA_VBL);
if (m_video_dma_on)
vidc_video_tick();
}
}
WRITE_LINE_MEMBER( archimedes_state::sound_drq )
{
if (state)
vidc_audio_tick();
}
/* video DMA */
// TODO: what type of DMA this is, burst or cycle steal? Docs doesn't explain it (4 usec is the DRAM refresh). */
// TODO: Erotictac and Poizone sets up vidinit register AFTER vidend, for double buffering? (fixes Poizone "Eterna" logo display on attract)
// TODO: understand how to make quazer to work (sets video DMA param in-flight)
void archimedes_state::vidc_video_tick()
{
address_space &space = m_maincpu->space(AS_PROGRAM);
uint32_t size;
uint32_t offset_ptr;
size = (m_vidc_vidend - m_vidc_vidstart + 0x10) & 0x1fffff;
offset_ptr = m_vidc_vidinit;
if(offset_ptr >= m_vidc_vidend+0x10) // TODO: correct?
offset_ptr = m_vidc_vidstart;
//popmessage("%08x %08x %08x",m_vidc_vidstart,m_vidc_vidinit,m_vidc_vidend);
for(m_vidc_vidcur = 0;m_vidc_vidcur < size;m_vidc_vidcur++)
{
m_vidc->write_vram(m_vidc_vidcur, space.read_byte(offset_ptr));
offset_ptr++;
if(offset_ptr >= m_vidc_vidend+0x10) // TODO: correct?
offset_ptr = m_vidc_vidstart;
}
if(m_cursor_enabled == true)
{
uint32_t ccur_size = m_vidc->get_cursor_size();
for(uint32_t ccur = 0; ccur < ccur_size; ccur++)
m_vidc->write_cram(ccur, space.read_byte(m_vidc_cinit+ccur));
}
}
/* audio DMA */
void archimedes_state::vidc_audio_tick()
{
address_space &space = m_maincpu->space(AS_PROGRAM);
uint8_t ch;
for(ch=0; ch<8; ch++)
m_vidc->write_dac(ch, (space.read_byte(m_vidc_sndcur + ch)));
m_vidc_sndcur+=8;
if (m_vidc_sndcur >= m_vidc_sndendcur)
{
archimedes_request_irq_b(ARCHIMEDES_IRQB_SOUND_EMPTY);
// TODO: nuke this implementation detail, repeated below
m_vidc->update_sound_mode(m_audio_dma_on);
if(!m_audio_dma_on)
{
for(ch=0; ch<8; ch++)
m_vidc->clear_dac(ch);
}
else
{
//printf("Chaining to next: start %x end %x\n", m_vidc_sndstart, m_vidc_sndend);
m_vidc_sndcur = m_vidc_sndstart;
m_vidc_sndendcur = m_vidc_sndend;
}
}
}
void archimedes_state::a310_set_timer(int tmr)
{
double freq;
switch(tmr)
{
case 0:
case 1:
m_timer[tmr]->adjust(attotime::from_usec(m_ioc_timercnt[tmr]/2), tmr); // TODO: ARM timings are quite off there, it should be latch and not latch/2
break;
case 2:
freq = 1000000.0 / (double)(m_ioc_timercnt[tmr]+1);
m_timer[tmr]->adjust(attotime::from_hz(freq), tmr);
break;
case 3:
freq = 1000000.0 / (double)((m_ioc_timercnt[tmr]+1)*16);
m_timer[tmr]->adjust(attotime::from_hz(freq), tmr);
break;
}
}
// param
void archimedes_state::ioc_timer(int param)
{
// all timers always run
a310_set_timer(param);
// keep FIQ line ASSERTED if there are active requests
if (m_ioc_regs[FIQ_STATUS] & m_ioc_regs[FIQ_MASK])
archimedes_request_fiq(0);
// but only timers 0 and 1 generate IRQs
switch (param)
{
case 0:
archimedes_request_irq_a(ARCHIMEDES_IRQA_TIMER0);
break;
case 1:
archimedes_request_irq_a(ARCHIMEDES_IRQA_TIMER1);
break;
}
}
void archimedes_state::archimedes_reset()
{
int i;
m_memc_latchrom = 1; // map in the boot ROM
// kill all memc mappings
for (i = 0; i < (32*1024*1024)/(4096); i++)
{
m_memc_pages[i] = -1; // indicate unmapped
}
m_ioc_regs[IRQ_STATUS_A] = 0x10 | 0x80; //set up POR (Power On Reset) and Force IRQ at start-up
m_ioc_regs[IRQ_STATUS_B] = 0x00; //set up IL[1] On
m_ioc_regs[FIQ_STATUS] = 0x80; //set up Force FIQ
m_ioc_regs[CONTROL] = 0xff;
}
void archimedes_state::archimedes_init()
{
m_memc_pagesize = 0;
m_timer[0] = timer_alloc(TIMER_IOC);
m_timer[1] = timer_alloc(TIMER_IOC);
m_timer[2] = timer_alloc(TIMER_IOC);
m_timer[3] = timer_alloc(TIMER_IOC);
m_timer[0]->adjust(attotime::never);
m_timer[1]->adjust(attotime::never);
m_timer[2]->adjust(attotime::never);
m_timer[3]->adjust(attotime::never);
}
uint32_t archimedes_state::archimedes_memc_logical_r(offs_t offset)
{
uint32_t page, poffs;
// are we mapping in the boot ROM?
if (m_memc_latchrom)
{
uint32_t *rom;
rom = (uint32_t *)m_region_maincpu->base();
return rom[offset & 0x1fffff];
}
else
{
// figure out the page number and offset in the page
page = (offset<<2) / page_sizes[m_memc_pagesize];
poffs = (offset<<2) % page_sizes[m_memc_pagesize];
// printf("Reading offset %x (addr %x): page %x (size %d %d) offset %x ==> %x %x\n", offset, offset<<2, page, memc_pagesize, page_sizes[memc_pagesize], poffs, memc_pages[page], memc_pages[page]*page_sizes[memc_pagesize]);
if (m_memc_pages[page] != -1)
{
return m_archimedes_memc_physmem[((m_memc_pages[page] * page_sizes[m_memc_pagesize]) + poffs)>>2];
}
else
{
//printf("ARCHIMEDES_MEMC: Reading unmapped page %02x\n",page);
return 0xdeadbeef;
}
}
// never executed
//return 0;
}
void archimedes_state::archimedes_memc_logical_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
uint32_t page, poffs;
// if the boot ROM is mapped, ignore writes
if (m_memc_latchrom)
{
return;
}
else
{
// figure out the page number and offset in the page
page = (offset<<2) / page_sizes[m_memc_pagesize];
poffs = (offset<<2) % page_sizes[m_memc_pagesize];
// printf("Writing offset %x (addr %x): page %x (size %d %d) offset %x ==> %x %x\n", offset, offset<<2, page, memc_pagesize, page_sizes[memc_pagesize], poffs, memc_pages[page], memc_pages[page]*page_sizes[memc_pagesize]);
if (m_memc_pages[page] != -1)
{
COMBINE_DATA(&m_archimedes_memc_physmem[((m_memc_pages[page] * page_sizes[m_memc_pagesize]) + poffs)>>2]);
}
else
{
//printf("ARCHIMEDES_MEMC: Writing unmapped page %02x, what do we do?\n",page);
}
}
}
/* Aristocrat Mark 5 - same as normal AA except with Dram emulator */
uint32_t archimedes_state::aristmk5_drame_memc_logical_r(offs_t offset)
{
uint32_t page, poffs;
// are we mapping in the boot ROM?
if (m_memc_latchrom)
{
uint32_t *rom;
rom = (uint32_t *)m_region_maincpu->base();
return rom[offset & 0x1fffff];
}
else
{
// figure out the page number and offset in the page
page = (offset<<2) / page_sizes[m_memc_pagesize];
poffs = (offset<<2) % page_sizes[m_memc_pagesize];
if (m_memc_pages[page] != -1)
{
/******************* DRAM Emulator - gal20v - Aristocrat Mark 5 ************************
A Dynamic RAM emulator is provided which avoids the need to execute code
in DRAM in those regulatory environments where it is not needed.
When pin 5 of U36 ( gal20v ) is low, the pin 25 output is high and enables the
logic buffer inputs and provides a fixed jmp address to a plurality
of rom addresses ( 0xEAD0000A shown on logic buffer arrangement in schematics )
In this state, DRAM memory space is disabled.
****************************************************************************************/
if(!(m_memc_pages[page] & 0x10) && (offset <= 0x3ff))
return 0xEAD0000A;
return m_archimedes_memc_physmem[((m_memc_pages[page] * page_sizes[m_memc_pagesize]) + poffs)>>2];
}
else
{
//printf("ARCHIMEDES_MEMC: Reading unmapped page %02x\n",page);
return 0xdeadbeef;
}
}
// never executed
//return 0;
}
void archimedes_state::archimedes_driver_init()
{
m_archimedes_memc_physmem = reinterpret_cast<uint32_t *>(memshare("physicalram")->ptr());
// address_space &space = m_maincpu->space(AS_PROGRAM);
// space.set_direct_update_handler(direct_update_delegate(&a310_setopbase, &machine));
}
static const char *const ioc_regnames[] =
{
"(rw) Control", // 0
"(read) Keyboard receive (write) keyboard send", // 4
"?",
"?",
"(read) IRQ status A", // 10
"(read) IRQ request A (write) IRQ clear", // 14
"(rw) IRQ mask A", // 18
"?",
"(read) IRQ status B", // 20
"(read) IRQ request B", // 24
"(rw) IRQ mask B", // 28
"?",
"(read) FIQ status", // 30
"(read) FIQ request", // 34
"(rw) FIQ mask", // 38
"?",
"(read) Timer 0 count low (write) Timer 0 latch low", // 40
"(read) Timer 0 count high (write) Timer 0 latch high", // 44
"(write) Timer 0 go command", // 48
"(write) Timer 0 latch command", // 4c
"(read) Timer 1 count low (write) Timer 1 latch low", // 50
"(read) Timer 1 count high (write) Timer 1 latch high", // 54
"(write) Timer 1 go command", // 58
"(write) Timer 1 latch command", // 5c
"(read) Timer 2 count low (write) Timer 2 latch low", // 60
"(read) Timer 2 count high (write) Timer 2 latch high", // 64
"(write) Timer 2 go command", // 68
"(write) Timer 2 latch command", // 6c
"(read) Timer 3 count low (write) Timer 3 latch low", // 70
"(read) Timer 3 count high (write) Timer 3 latch high", // 74
"(write) Timer 3 go command", // 78
"(write) Timer 3 latch command" // 7c
};
void archimedes_state::latch_timer_cnt(int tmr)
{
double time = m_timer[tmr]->elapsed().as_double();
time *= 2000000.0; // find out how many 2 MHz ticks have gone by
m_ioc_timerout[tmr] = m_ioc_timercnt[tmr] - (uint32_t)time;
}
bool archimedes_state::check_floppy_ready()
{
floppy_image_device *floppy = nullptr;
if(!m_fdc)
return false;
switch(m_floppy_select & 3)
{
case 0:
floppy = m_floppy0->get_device(); break;
case 1:
floppy = m_floppy1->get_device(); break;
}
if(floppy)
return !floppy->ready_r();
return false;
}
/* TODO: should be a 8-bit handler */
uint32_t archimedes_state::ioc_ctrl_r(offs_t offset)
{
if(IOC_LOG)
logerror("IOC: R %s = %02x (PC=%x) %02x\n", ioc_regnames[offset&0x1f], m_ioc_regs[offset&0x1f], m_maincpu->pc(),offset & 0x1f);
switch (offset & 0x1f)
{
case CONTROL:
{
uint8_t i2c_data = 1;
bool floppy_ready_state;
if ( m_i2cmem )
{
i2c_data = (m_i2cmem->read_sda() & 1);
}
floppy_ready_state = check_floppy_ready();
return (m_vidc->flyback_r()<<7) | (m_ioc_regs[CONTROL] & 0x78) | (floppy_ready_state<<2) | (m_i2c_clk<<1) | i2c_data;
}
case KART: // keyboard read
return m_kart->read();
case IRQ_STATUS_A:
return (m_ioc_regs[IRQ_STATUS_A] & 0x7f) | 0x80; // Force IRQ is always '1'
case IRQ_REQUEST_A:
return (m_ioc_regs[IRQ_STATUS_A] & m_ioc_regs[IRQ_MASK_A]);
case IRQ_MASK_A:
return (m_ioc_regs[IRQ_MASK_A]);
case IRQ_STATUS_B:
return (m_ioc_regs[IRQ_STATUS_B]);
case IRQ_REQUEST_B:
return (m_ioc_regs[IRQ_STATUS_B] & m_ioc_regs[IRQ_MASK_B]);
case IRQ_MASK_B:
return (m_ioc_regs[IRQ_MASK_B]);
case FIQ_STATUS:
return (m_ioc_regs[FIQ_STATUS] & 0x7f) | 0x80; // Force FIQ is always '1'
case FIQ_REQUEST:
return (m_ioc_regs[FIQ_STATUS] & m_ioc_regs[FIQ_MASK]);
case FIQ_MASK:
return (m_ioc_regs[FIQ_MASK]);
case T0_LATCH_LO: return m_ioc_timerout[0]&0xff;
case T0_LATCH_HI: return (m_ioc_timerout[0]>>8)&0xff;
case T1_LATCH_LO: return m_ioc_timerout[1]&0xff;
case T1_LATCH_HI: return (m_ioc_timerout[1]>>8)&0xff;
case T2_LATCH_LO: return m_ioc_timerout[2]&0xff;
case T2_LATCH_HI: return (m_ioc_timerout[2]>>8)&0xff;
case T3_LATCH_LO: return m_ioc_timerout[3]&0xff;
case T3_LATCH_HI: return (m_ioc_timerout[3]>>8)&0xff;
default:
if(!IOC_LOG)
logerror("IOC: R %s = %02x (PC=%x) %02x\n", ioc_regnames[offset&0x1f], m_ioc_regs[offset&0x1f], m_maincpu->pc(), offset & 0x1f);
break;
}
return m_ioc_regs[offset&0x1f];
}
/* TODO: should be a 8-bit handler */
void archimedes_state::ioc_ctrl_w(offs_t offset, uint32_t data)
{
if(IOC_LOG)
logerror("IOC: W %02x @ reg %s (PC=%x)\n", data&0xff, ioc_regnames[offset&0x1f], m_maincpu->pc());
switch (offset&0x1f)
{
case CONTROL: // I2C bus control
//logerror("IOC I2C: CLK %d DAT %d\n", (data>>1)&1, data&1);
if ( m_i2cmem )
{
m_i2cmem->write_sda(data & 0x01);
m_i2cmem->write_scl((data & 0x02) >> 1);
}
m_i2c_clk = (data & 2) >> 1;
//TODO: does writing bit 2 here causes a fdc force ready?
/*
-x-- ---- Printer ack
--x- ---- Sound mute
---x ---- Aux I/O connector
---- -x-- Floppy ready
---- --x- I2C clock
---- ---x I2C data
*/
//m_ioc_regs[CONTROL] = data & 0x38;
//if(data & 0x40)
// popmessage("Muting sound, contact MAME/MESSdev");
break;
case KART:
m_kart->write(data);
break;
case IRQ_MASK_A:
m_ioc_regs[IRQ_MASK_A] = data & 0xff;
/* bit 7 forces an IRQ trap */
archimedes_request_irq_a((data & 0x80) ? ARCHIMEDES_IRQA_FORCE : 0);
//if(data & 0x08) //set up the VBLANK timer
// m_vbl_timer->adjust(m_screen->time_until_pos(m_vidc_vblank_time));
break;
case IRQ_MASK_B:
m_ioc_regs[IRQ_MASK_B] = data & 0xff;
archimedes_request_irq_b(0);
break;
case FIQ_MASK:
m_ioc_regs[FIQ_MASK] = data & 0xff;
/* bit 7 forces a FIRQ trap */
archimedes_request_fiq((data & 0x80) ? ARCHIMEDES_FIQ_FORCE : 0);
break;
case IRQ_REQUEST_A: // IRQ clear A
m_ioc_regs[IRQ_STATUS_A] &= ~(data&0xff);
// check pending irqs
archimedes_request_irq_a(0);
break;
case T0_LATCH_LO:
case T0_LATCH_HI:
m_ioc_regs[offset&0x1f] = data & 0xff;
break;
case T1_LATCH_LO:
case T1_LATCH_HI:
m_ioc_regs[offset&0x1f] = data & 0xff;
break;
case T2_LATCH_LO:
case T2_LATCH_HI:
m_ioc_regs[offset&0x1f] = data & 0xff;
break;
case T3_LATCH_LO:
case T3_LATCH_HI:
m_ioc_regs[offset&0x1f] = data & 0xff;
break;
case T0_LATCH: // Timer 0 latch
latch_timer_cnt(0);
break;
case T1_LATCH: // Timer 1 latch
latch_timer_cnt(1);
break;
case T2_LATCH: // Timer 2 latch
latch_timer_cnt(2);
break;
case T3_LATCH: // Timer 3 latch
latch_timer_cnt(3);
break;
case T0_GO: // Timer 0 start
m_ioc_timercnt[0] = m_ioc_regs[T0_LATCH_HI]<<8 | m_ioc_regs[T0_LATCH_LO];
a310_set_timer(0);
break;
case T1_GO: // Timer 1 start
m_ioc_timercnt[1] = m_ioc_regs[T1_LATCH_HI]<<8 | m_ioc_regs[T1_LATCH_LO];
a310_set_timer(1);
break;
case T2_GO: // Timer 2 start
m_ioc_timercnt[2] = m_ioc_regs[T2_LATCH_HI]<<8 | m_ioc_regs[T2_LATCH_LO];
a310_set_timer(2);
break;
case T3_GO: // Timer 3 start
m_ioc_timercnt[3] = m_ioc_regs[T3_LATCH_HI]<<8 | m_ioc_regs[T3_LATCH_LO];
a310_set_timer(3);
break;
default:
if(!IOC_LOG)
logerror("IOC: W %02x @ reg %s (PC=%x)\n", data&0xff, ioc_regnames[offset&0x1f], m_maincpu->pc());
m_ioc_regs[offset&0x1f] = data & 0xff;
break;
}
}
uint32_t archimedes_state::archimedes_ioc_r(offs_t offset, uint32_t mem_mask)
{
uint32_t ioc_addr;
ioc_addr = offset*4;
switch((ioc_addr & 0x300000) >> 20)
{
/*82c711*/
case 0:
logerror("82c711 read at address %08x\n",ioc_addr);
return 0;
case 2:
case 3:
{
switch((ioc_addr & 0x70000) >> 16)
{
case 0: return ioc_ctrl_r(offset);
case 1:
if (m_fdc)
{
//printf("17XX: R @ addr %x mask %08x\n", offset*4, mem_mask);
return m_fdc->read((ioc_addr >> 2) & 0x03);
} else {
logerror("Read from FDC device?\n");
return 0;
}
case 2:
// RTFM joystick interface routes here
// TODO: slot interface for econet (reads registers 0 and 1 during boot)
switch(ioc_addr)
{
case 0x3a0000:
return 0xed; // ID for econet
case 0x3a0004:
return m_joy[0].read_safe(0xff);
case 0x3a0008:
// Top Banana reads there and do various checks,
// disallowing player 1 joy use if they fails (?)
return m_joy[1].read_safe(0xff);
}
logerror("IOC: Econet Read %08x at PC=%08x\n",ioc_addr, m_maincpu->pc());
return 0xffff;
case 3:
logerror("IOC: Serial Read\n");
return 0xffff;
case 4:
logerror("IOC: Internal Podule Read\n");
return 0xffff;
case 5:
if (m_fdc)
{
// TODO: IOEB slot interface
switch(ioc_addr & 0xfffc)
{
case 0x18: return 0xff; // FDC latch B
case 0x40: return 0xff; // FDC latch A
case 0x50: return 0; //fdc type, an 82c711 returns 5 here
case 0x70: return 0x0f; // monitor type, TBD
case 0x74: return 0xff; // unknown
case 0x78: // serial joystick?
case 0x7c:
logerror("FDC: reading Joystick port %04x at PC=%08x\n",ioc_addr, m_maincpu->pc());
return 0xff;
}
}
//printf("IOC: Internal Latches Read %08x\n",ioc_addr);
return 0xffff;
}
}
}
logerror("IOC: Unknown read at %08x\n",ioc_addr);
return 0;
}
void archimedes_state::archimedes_ioc_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
uint32_t ioc_addr;
ioc_addr = offset*4;
switch((ioc_addr & 0x300000) >> 20)
{
/*82c711*/
case 0:
logerror("82c711 write %08x to address %08x\n",data,ioc_addr);
return;
case 2:
case 3:
{
switch((ioc_addr & 0x70000) >> 16)
{
case 0: ioc_ctrl_w(offset,data); return;
case 1:
if (m_fdc)
{
//printf("17XX: %x to addr %x mask %08x\n", data, offset*4, mem_mask);
m_fdc->write((ioc_addr >> 2) & 0x03, data);
return;
}
else
{
logerror("Write to FDC device?\n");
}
return;
case 2:
logerror("IOC: Econet Write %02x at %08x\n",data,ioc_addr);
return;
case 3:
logerror("IOC: Serial Write %02x (%c) at %08x\n",data,data,ioc_addr);
return;
case 4:
logerror("IOC: Internal Podule Write\n");
return;
case 5:
if (m_fdc)
{
switch(ioc_addr & 0xfffc)
{
// serial joy port (!JS application)
case 0x10:
{
// compared to RTFM they reversed bits 0-3 (or viceversa, dunno what came out first)
// for pragmatic convenience we bitswap here, but this should really be a slot option at some point.
// TODO: understand how player 2 inputs routes, related somehow to CONTROL bit 6 (cfr. blitz in SW list)
// TODO: paradr2k polls here with bit 7 and fails detection (Vertical Twist)
uint8_t cur_joy_in = bitswap<8>(m_joy[0].read_safe(0xff),7,6,5,4,0,1,2,3);
m_joy_serial_data = (data & 0xff) ^ 0xff;
bool serial_on = false;
if (m_joy_serial_data == 0x20)
serial_on = true;
else if (m_joy_serial_data & cur_joy_in)
serial_on = true;
// wants printer irq for some reason (connected on parallel?)
if (serial_on == true)
{
archimedes_request_irq_a(ARCHIMEDES_IRQA_PRINTER_BUSY);
//m_ioc_regs[CONTROL] |= 0x40;
}
else
{
archimedes_clear_irq_a(ARCHIMEDES_IRQA_PRINTER_BUSY);
//m_ioc_regs[CONTROL] &= ~0x40;
}
return;
}
case 0x18: // latch B
/*
---- x--- floppy controller reset
---x ---- printer strobe
*/
m_fdc->dden_w(BIT(data, 1));
m_fdc->mr_w(BIT(data, 3));
if(data & ~0xa)
printf("%02x Latch B\n",data);
return;
case 0x40: // latch A
/*
-x-- ---- In Use Control (floppy?)
*/
floppy_image_device *floppy = nullptr;
if (!(data & 1)) { m_floppy_select = 0; floppy = m_floppy0->get_device(); }
if (!(data & 2)) { m_floppy_select = 1; floppy = m_floppy1->get_device(); }
if (!(data & 4)) { m_floppy_select = 2; floppy = nullptr; } // floppy 2
if (!(data & 8)) { m_floppy_select = 3; floppy = nullptr; } // floppy 3
m_fdc->set_floppy(floppy);
if(floppy)
{
floppy->mon_w(BIT(data, 5));
floppy->ss_w(!(BIT(data, 4)));
}
//bit 5 is motor on
return;
}
//printf("%08x\n",ioc_addr);
}
break;
}
}
}
logerror("(PC=%08x) I/O: W %x @ %x (mask %08x)\n", m_maincpu->pc(), data, (offset*4)+0x3000000, mem_mask);
}
void archimedes_state::archimedes_memc_w(offs_t offset, uint32_t data, uint32_t mem_mask)
{
// is it a register?
if ((data & 0x0fe00000) == 0x03600000)
{
switch ((data >> 17) & 7)
{
case 0: /* video init */
m_vidc_vidinit = 0x2000000 | ((data>>2)&0x7fff)*16;
//printf("MEMC: VIDINIT %08x\n",m_vidc_vidinit);
break;
case 1: /* video start */
m_vidc_vidstart = 0x2000000 | (((data>>2)&0x7fff)*16);
//printf("MEMC: VIDSTART %08x\n",m_vidc_vidstart);
break;
case 2: /* video end */
m_vidc_vidend = 0x2000000 | (((data>>2)&0x7fff)*16);
//printf("MEMC: VIDEND %08x\n",m_vidc_vidend);
break;
case 3: /* cursor init */
m_cursor_enabled = true;
m_vidc->set_cursor_enable(m_cursor_enabled);
m_vidc_cinit = 0x2000000 | (((data>>2)&0x7fff)*16);
//printf("MEMC: CURSOR INIT %08x\n",((data>>2)&0x7fff)*16);
break;
case 4: /* sound start */
archimedes_clear_irq_b(ARCHIMEDES_IRQB_SOUND_EMPTY);
m_vidc_sndstart = 0x2000000 | ((data>>2)&0x7fff)*16;
//printf("MEMC: SNDSTART %08x\n",m_vidc_sndstart);
break;
case 5: /* sound end */
// end buffer is actually +16 bytes wrt sound start
// TODO: it actually don't apply for ertictac and poizone?
m_vidc_sndend = 0x2000000 | (((data>>2)+1)&0x7fff)*16;
//printf("MEMC: SNDEND %08x\n",m_vidc_sndend);
break;
case 6:
//printf("MEMC: SNDPTR\n");
m_vidc_sndcur = m_vidc_sndstart;
m_vidc_sndendcur = m_vidc_sndend;
archimedes_request_irq_b(ARCHIMEDES_IRQB_SOUND_EMPTY);
break;
case 7: /* Control */
m_memc_pagesize = ((data>>2) & 3);
logerror("(PC = %08x) MEMC: %x to Control (page size %d, %s, %s)\n", m_maincpu->pc(), data & 0x1ffc, page_sizes[m_memc_pagesize], ((data>>10)&1) ? "Video DMA on" : "Video DMA off", ((data>>11)&1) ? "Sound DMA on" : "Sound DMA off");
m_video_dma_on = BIT(data, 10);
m_audio_dma_on = BIT(data, 11);
if (m_video_dma_on)
{
m_vidc_vidcur = 0;
// TODO: update internally
}
else
{
m_cursor_enabled = false;
m_vidc->set_cursor_enable(m_cursor_enabled);
}
m_vidc->update_sound_mode(m_audio_dma_on);
if (m_audio_dma_on)
{
//printf("MEMC: Starting audio DMA at %d uSec, buffer from %x to %x\n", ((m_vidc_regs[0xc0]&0xff)-2)*8, m_vidc_sndstart, m_vidc_sndend);
//printf("MEMC: audio DMA start, sound freq %d, sndhz = %f\n", (m_vidc_regs[0xc0] & 0xff)-2, sndhz);
m_vidc_sndcur = m_vidc_sndstart;
m_vidc_sndendcur = m_vidc_sndend;
}
break;
default:
logerror("MEMC: %x to Unk reg %d\n", data&0x1ffff, (data >> 17) & 7);
break;
}
}
else
{
logerror("MEMC non-reg: W %x @ %x (mask %08x)\n", data, offset, mem_mask);
}
}
/*
22 2222 1111 1111 1100 0000 0000
54 3210 9876 5432 1098 7654 3210
4k page: 11 1LLL LLLL LLLL LLAA MPPP PPPP
8k page: 11 1LLL LLLL LLLM LLAA MPPP PPPP
16k page: 11 1LLL LLLL LLxM LLAA MPPP PPPP
32k page: 11 1LLL LLLL LxxM LLAA MPPP PPPP
3 8 2 9 0 f f
L - logical page
P - physical page
A - access permissions
M - MEMC number (for machines with multiple MEMCs)
The logical page is encoded with bits 11+10 being the most significant bits
(in that order), and the rest being bit 22 down.
The physical page is encoded differently depending on the page size :
4k page: bits 6-0 being bits 6-0
8k page: bits 6-1 being bits 5-0, bit 0 being bit 6
16k page: bits 6-2 being bits 4-0, bits 1-0 being bits 6-5
32k page: bits 6-3 being bits 4-0, bit 0 being bit 4, bit 2 being bit 5, bit
1 being bit 6
*/
void archimedes_state::archimedes_memc_page_w(uint32_t data)
{
uint32_t log, phys, memc;
// perms = (data & 0x300)>>8;
log = phys = memc = 0;
switch (m_memc_pagesize)
{
case 0:
phys = data & 0x7f;
log = ((data & 0x7ff000)>>12) | ((data & 0xc00)<<1);
memc = (data & 0x80) ? 1 : 0;
break;
case 1:
phys = ((data & 0x7f) >> 1) | ((data & 1) << 6);
log = ((data & 0x7fe000)>>13) | (data & 0xc00);
memc = ((data & 0x80) ? 1 : 0) | ((data & 0x1000) ? 2 : 0);
break;
case 2:
phys = ((data & 0x7f) >> 2) | ((data & 3) << 5);
log = ((data & 0x7fc000)>>14) | ((data & 0xc00)>>1);
memc = ((data & 0x80) ? 1 : 0) | ((data & 0x1000) ? 2 : 0);
break;
case 3:
phys = ((data & 0x7f) >> 3) | ((data & 1)<<4) | ((data & 2) << 5) | ((data & 4)<<3);
log = ((data & 0x7f8000)>>15) | ((data & 0xc00)>>2);
memc = ((data & 0x80) ? 1 : 0) | ((data & 0x1000) ? 2 : 0);
//printf("Mapping %08X to %08X\n",0x2000000+(phys*32768),(((data >> 15)&0xff)|((data >> 2)&0x300)));
break;
}
// log >>= (12 + memc_pagesize);
// always make sure ROM mode is disconnected when this occurs
m_memc_latchrom = 0;
// now go ahead and set the mapping in the page table
m_memc_pages[log] = phys + (memc*0x80);
// printf("PC=%08x = MEMC_PAGE(%d): W %08x: log %x to phys %x, MEMC %d, perms %d\n", m_maincpu->pc(),memc_pagesize, data, log, phys, memc, perms);
}