// license:BSD-3-Clause
// copyright-holders:Nathan Woods,Frank Palazzolo
#include "emu.h"
#include "video/stic.h"
#include "video/tms9927.h"
#include "includes/intv.h"
#include "cpu/cp1610/cp1610.h"
// Dual Port Memory handlers
void intv_state::intvkbd_dualport16_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
unsigned char *RAM;
COMBINE_DATA(&m_intvkbd_dualport_ram[offset]);
/* copy the LSB over to the 6502 OP RAM, in case they are opcodes */
RAM = m_region_keyboard->base();
RAM[offset] = (uint8_t) (data >> 0);
}
uint8_t intv_state::intvkbd_dualport8_lsb_r(offs_t offset)
{
return (uint8_t) (m_intvkbd_dualport_ram[offset] >> 0);
}
void intv_state::intvkbd_dualport8_lsb_w(offs_t offset, uint8_t data)
{
unsigned char *RAM;
m_intvkbd_dualport_ram[offset] &= ~0x00FF;
m_intvkbd_dualport_ram[offset] |= ((uint16_t) data) << 0;
/* copy over to the 6502 OP RAM, in case they are opcodes */
RAM = m_region_keyboard->base();
RAM[offset] = data;
}
uint8_t intv_state::intvkbd_dualport8_msb_r(offs_t offset)
{
return (m_intvkbd_dualport_ram[offset+0x200]&0x0300)>>8;
}
void intv_state::intvkbd_dualport8_msb_w(offs_t offset, uint8_t data)
{
unsigned int mask = m_intvkbd_dualport_ram[offset+0x200] & 0x00ff;
m_intvkbd_dualport_ram[offset+0x200] = mask | ((data<<8)&0x0300);
}
// I/O for the Tape Drive
// (to be moved to a device)
struct tape_drive_state_type
{
/* read state */
int read_data; /* 0x4000 */
int ready; /* 0x4001 */
int leader_detect; /* 0x4002 */
int tape_missing; /* 0x4003 */
int playing; /* 0x4004 */
int no_data; /* 0x4005 */
/* write state */
int motor_state; /* 0x4020-0x4022 */
int writing; /* 0x4023 */
int audio_b_mute; /* 0x4024 */
int audio_a_mute; /* 0x4025 */
int channel_select; /* 0x4026 */
int erase; /* 0x4027 */
int write_data; /* 0x4040 */
/* bit_counter */
int bit_counter;
} tape_drive;
//static const char *const tape_motor_mode_desc[8] =
//{
// "IDLE", "IDLE", "IDLE", "IDLE",
// "EJECT", "PLAY/RECORD", "REWIND", "FF"
//};
uint8_t intv_state::intvkbd_io_r(offs_t offset)
{
unsigned char rv = 0x00;
switch (offset)
{
// These next 8 locations all map to bit7
case 0x000:
// "Data from Cassette"
// Tape drive does the decoding to bits
//rv = m_io_test->read() & 0x80;
rv = tape_drive.read_data << 7;
break;
case 0x001:
// "Watermark"
// 0 = Drive Busy Executing Command?, 1 = Drive Ok?
//rv = (m_io_test->read() & 0x40) << 1;
rv = tape_drive.ready << 7;
break;
case 0x002:
// "End of Tape"
// 0 = Recordable surface, 1 = Leader Detect
// (Leader is transparent, optical sensor)
//rv = (m_io_test->read() & 0x20) << 2;
rv = tape_drive.leader_detect << 7;
//logerror("TAPE: Read %02x from 0x40%02x - Sense 2?\n",rv,offset);
break;
case 0x003:
// "Cassette Present"
// 0 = Tape Present, 1 = Tape Not Present
//rv = (m_io_test->read() & 0x10) << 3;
rv = tape_drive.tape_missing << 7;
//logerror("TAPE: Read %02x from 0x40%02x - Tape Present\n",rv,offset);
break;
case 0x004:
// "NOT Inter Record Gap (IRG)"
// 0 = Not Playing/Recording?, 1 = Playing/Recording?
//rv = (m_io_test->read() & 0x08) << 4;
rv = tape_drive.playing << 7;
//logerror("TAPE: Read %02x from 0x40%02x - Comp (339/1)\n",rv,offset);
break;
case 0x005:
// "Dropout"
// 0 = Data Detect, 1 = No Data
//rv = (m_io_test->read() & 0x04) << 5;
rv = tape_drive.no_data << 7;
//logerror("TAPE: Read %02x from 0x40%02x - Clocked Comp (339/13)\n",rv,offset);
break;
case 0x006:
// "NOT Clock Interrupt"
if (m_sr1_int_pending)
rv = 0x00;
else
rv = 0x80;
//logerror("TAPE: Read %02x from 0x40%02x - SR1 Int Pending\n",rv,offset);
break;
case 0x007:
// "NOT Tape Interrupt"
if (m_tape_int_pending)
rv = 0x00;
else
rv = 0x80;
//logerror("TAPE: Read %02x from 0x40%02x - Tape? Int Pending\n",rv,offset);
break;
case 0x060:
// "Read Keyboard"
rv = 0xff;
if (m_intvkbd_keyboard_col < 10)
rv = m_intv_keyboard[m_intvkbd_keyboard_col]->read();
break;
case 0x80:
// "Clear Tape Interrupt"
rv = 0x00;
//logerror("TAPE: Read %02x from 0x40%02x, clear tape int pending\n",rv,offset);
m_tape_int_pending = 0;
break;
case 0xa0:
// "Clear Clock Interrupt"
rv = 0x00;
//logerror("TAPE: Read %02x from 0x40%02x, clear SR1 int pending\n",rv,offset);
m_sr1_int_pending = 0;
break;
default:
//logerror("Unknown read %02x from 0x40%02x\n",rv,offset);
break;
}
return rv;
}
void intv_state::intvkbd_io_w(offs_t offset, uint8_t data)
{
switch (offset)
{
// Bits from offset $20 to $47 are all bit0, write only
// These are all set to zero by system reset
case 0x020:
// "Tape Drive Control: Enable"
tape_drive.motor_state &= 3;
if (data & 1)
tape_drive.motor_state |= 4;
//logerror("TAPE: Motor Mode: %s\n",tape_motor_mode_desc[m_tape_motor_mode]);
break;
case 0x021:
// "Tape Drive Control: Forward"
tape_drive.motor_state &= 5;
if (data & 1)
tape_drive.motor_state |= 2;
//logerror("TAPE: Motor Mode: %s\n",tape_motor_mode_desc[m_tape_motor_mode]);
break;
case 0x022:
// "Tape Drive Control: Fast"
tape_drive.motor_state &= 6;
if (data & 1)
tape_drive.motor_state |= 1;
//logerror("TAPE: Motor Mode: %s\n",tape_motor_mode_desc[m_tape_motor_mode]);
break;
case 0x023:
// "Tape Drive Control: Record"
// 0=Read, 1=Write
tape_drive.writing = (data & 1);
break;
case 0x024:
// "Tape Drive Control: Mute 1"
// 0=Enable Channel B Audio, 1=Mute
tape_drive.audio_b_mute = (data & 1);
break;
case 0x025:
// "Tape Drive Control: Mute 2"
// 0=Enable Channel A Audio, 1=Mute
tape_drive.audio_a_mute = (data & 1);
break;
case 0x026:
// "Tape Drive Control: Mode"
// If read mode:
// 0=Read Channel B Data, 1 = Read Channel A Data
// If write mode:
// 0=Write Channel B data, 1 = Record Channel B Audio
tape_drive.channel_select = (data & 1);
break;
case 0x027:
// "Tape Drive Control: Erase"
tape_drive.erase = (data & 1);
break;
case 0x040:
// Data to Tape
tape_drive.write_data = (data & 1);
break;
case 0x041:
// "Tape Interrupt Enable"
//if (data & 1)
//logerror("TAPE: Tape Interrupts Enabled\n");
//else
//logerror("TAPE: Tape Interrupts Disabled\n");
m_tape_interrupts_enabled = (data & 1);
break;
case 0x042:
// "NOT External Interrupt Enable"
//if (data & 1)
//logerror("TAPE: Cart Bus Interrupts Disabled\n");
//else
//logerror("TAPE: Cart Bus Interrupts Enabled\n");
break;
case 0x043:
// "NOT Blank Screen"
if (data & 0x01)
m_intvkbd_text_blanked = 0;
else
m_intvkbd_text_blanked = 1;
break;
case 0x044:
m_intvkbd_keyboard_col &= 0x0e;
m_intvkbd_keyboard_col |= (data&0x01);
break;
case 0x045:
m_intvkbd_keyboard_col &= 0x0d;
m_intvkbd_keyboard_col |= ((data&0x01)<<1);
break;
case 0x046:
m_intvkbd_keyboard_col &= 0x0b;
m_intvkbd_keyboard_col |= ((data&0x01)<<2);
break;
case 0x047:
m_intvkbd_keyboard_col &= 0x07;
m_intvkbd_keyboard_col |= ((data&0x01)<<3);
break;
case 0x80:
// "Clear Tape Interrupt"
//logerror("TAPE: Write to 0x40%02x, clear tape int pending\n",offset);
m_tape_int_pending = 0;
break;
case 0xa0:
// "Clear Clock Interrupt"
//logerror("TAPE: Write to 0x40%02x, clear SR1 int pending\n",offset);
m_sr1_int_pending = 0;
break;
default:
//logerror("%04X: Unknown write %02x to 0x40%02x\n",m_keyboard->pc(),data,offset);
break;
}
}
#if 0
static int max_bits = 0;
static unsigned char *tape_data;
void get_tape_bit(int position, int channel, int *data_present, int *data)
{
int byte = (position >> 2)*2 + channel;
int data_present_mask = 1 << ((3-(position % 4))*2 + 1);
int data_mask = 1 << ((3-(position % 4))*2);
//printf("%d\t0x%02x 0x%02x\n",byte,data_present_mask,data_mask);
if (tape_data[byte] & data_present_mask)
*data_present = 1;
else
*data_present = 0;
if (tape_data[byte] & data_mask)
*data = 1;
else
*data = 0;
}
void set_tape_bit(int position, int data)
{
int byte = (position >> 2)*2 + 1;
int data_present_mask = 1 << ((3-(position % 4))*2 + 1);
int data_mask = 1 << ((3-(position % 4))*2);
tape_data[byte] |= data_present_mask;
if (data)
tape_data[byte] |= data_mask;
else
tape_data[byte] &= (~data_mask);
}
#endif
#if defined(LATER)
int intvkbd_tape_init(int id)
{
FILE *tapefile;
int filesize;
if (!(tapefile = image_fopen (IO_CASSETTE, id, OSD_FILETYPE_IMAGE, OSD_FOPEN_READ)))
{
return INIT_FAIL;
}
filesize = osd_fsize(tapefile);
tape_data = (unsigned char *)malloc(filesize);
osd_fread(tapefile, tape_data, filesize);
osd_fclose(tapefile);
max_bits = 2*filesize;
tape_drive.tape_missing = 0;
tape_drive.leader_detect = 0;
tape_drive.ready = 1;
tape_drive.bit_counter = 0;
return INIT_PASS;
}
void intvkbd_tape_exit(int id)
{
FILE *tapefile;
int filesize;
if (tape_data)
{
if (!(tapefile = image_fopen (IO_CASSETTE, id, OSD_FILETYPE_IMAGE, OSD_FOPEN_RW)))
{
filesize = osd_fsize(tapefile);
osd_fwrite(tapefile, tape_data, filesize);
osd_fclose(tapefile);
free(tape_data);
tape_data = 0;
max_bits = 0;
tape_drive.tape_missing = 1;
tape_drive.bit_counter = 0;
}
}
}
void update_tape_drive(void)
{
/* temp */
if (tape_drive.writing)
{
if (tape_drive.channel_select == 0) /* data */
{
set_tape_bit(tape_drive.bit_counter,tape_drive.write_data);
}
else
{
/* recording audio - TBD */
}
}
else
{
int channel;
int data_present;
int data;
channel = tape_drive.channel_select ^ 1;
get_tape_bit(tape_drive.bit_counter,channel,&data_present,&data);
tape_drive.no_data = data_present ^ 1;
tape_drive.read_data = data;
/* temporary */
tape_drive.playing = data_present ^ 1;
}
if (tape_drive.motor_state == 5) /* Playing */
{
tape_drive.bit_counter++;
if (tape_drive.bit_counter >= max_bits)
tape_drive.bit_counter = max_bits-1;
}
if (tape_drive.motor_state == 6) /* Rewinding */
{
tape_drive.bit_counter-=4;
//tape_drive.bit_counter--;
if (tape_drive.bit_counter < 0)
tape_drive.bit_counter = 0;
}
if (tape_drive.motor_state == 7) /* FastFwd */
{
tape_drive.bit_counter+=2;
//tape_drive.bit_counter++;
if (tape_drive.bit_counter >= max_bits)
tape_drive.bit_counter = max_bits-1;
}
if ((tape_drive.bit_counter == 0) || (tape_drive.bit_counter == max_bits-1))
tape_drive.leader_detect = 1;
else
tape_drive.leader_detect = 0;
}
#endif
////////////
uint8_t intv_state::intvkbd_periph_r(offs_t offset)
{
uint8_t value = 0;
switch(offset) {
case 0x06:
if (m_printer_not_busy_enable)
if (m_printer_not_busy)
value |= 0x80;
if (m_printer_no_paper)
value |= 0x10;
//logerror("PeriphRead: 0x%04x->0x%02x\n",offset,value);
// After one query of busy,
// next time the state is not_busy
if (!m_printer_not_busy)
m_printer_not_busy = true;
return value;
break;
case 0x07:
default:
//logerror("PeriphRead: 0x%04x->0x%02x\n",offset,0xff);
return 0xff;
break;
}
}
void intv_state::intvkbd_periph_w(offs_t offset, uint8_t data)
{
switch(offset) {
case 0x06:
//logerror("PeriphWrite: 0x%04x->0x%02x\n",offset,data);
if (data & 0x20)
m_printer_not_busy_enable = true;
else
m_printer_not_busy_enable = false;
break;
case 0x07:
//logerror("Printing: 0x%02x, %c\n",data,data);
// For testing, print to stdout
fputc(data, stdout);
fflush(stdout);
m_printer_not_busy = false;
break;
default:
//logerror("PeriphWrite: 0x%04x->0x%02x\n",offset,data);
break;
}
}
uint16_t intv_state::intv_stic_r(offs_t offset)
{
if (m_bus_copy_mode || !m_stic->read_stic_handshake())
return m_stic->read(offset);
else
return offset;
}
void intv_state::intv_stic_w(offs_t offset, uint16_t data)
{
if (m_bus_copy_mode || !m_stic->read_stic_handshake())
m_stic->write(offset, data);
}
uint16_t intv_state::intv_gram_r(offs_t offset)
{
//logerror("read: %d = GRAM(%d)\n",state->m_gram[offset],offset);
if (m_bus_copy_mode || !m_stic->read_stic_handshake())
return m_stic->gram_read(offset);
else
return offset;
}
void intv_state::intv_gram_w(offs_t offset, uint16_t data)
{
if (m_bus_copy_mode || !m_stic->read_stic_handshake())
m_stic->gram_write(offset, data);
}
uint16_t intv_state::intv_ram8_r(offs_t offset)
{
//logerror("%x = ram8_r(%x)\n",state->m_ram8[offset],offset);
return (int)m_ram8[offset];
}
void intv_state::intv_ram8_w(offs_t offset, uint16_t data)
{
//logerror("ram8_w(%x) = %x\n",offset,data);
m_ram8[offset] = data&0xff;
}
uint16_t intv_state::intv_ram16_r(offs_t offset)
{
//logerror("%x = ram16_r(%x)\n",state->m_ram16[offset],offset);
return (int)m_ram16[offset];
}
void intv_state::intv_ram16_w(offs_t offset, uint16_t data)
{
//logerror("%g: WRITING TO GRAM offset = %d\n",machine.time(),offset);
//logerror("ram16_w(%x) = %x\n",offset,data);
m_ram16[offset] = data & 0xffff;
}
uint8_t intv_state::intvkb_iocart_r(offs_t offset)
{
if (m_iocart1->exists())
return m_iocart1->read_rom(offset);
else if (m_iocart2->exists())
return m_iocart2->read_rom(offset);
else
return m_region_keyboard->as_u8(offset + 0xe000);
}
uint16_t intv_state::iab_r()
{
if (m_maincpu_reset)
{
// Reset vector
m_maincpu_reset = false;
return 0x1000;
}
// INTR/INTRM vector
return 0x1004;
}
/* Set Reset and INTR/INTRM Vector */
void intv_state::machine_reset()
{
m_maincpu_reset = true;
if (m_is_keybd)
{
m_printer_not_busy = true; // printer state
m_printer_no_paper = false; // printer state
m_printer_not_busy_enable = false; // printer interface state
}
}
void intv_state::machine_start()
{
save_item(NAME(m_bus_copy_mode));
save_item(NAME(m_backtab_row));
save_item(NAME(m_ram16));
save_item(NAME(m_sr1_int_pending));
save_item(NAME(m_ram8));
save_item(NAME(m_maincpu_reset));
// intvkbd
if (m_is_keybd)
{
for (int i = 0; i < 10; i++)
{
char str[5];
sprintf(str, "ROW%X", uint8_t(i));
m_intv_keyboard[i] = ioport(str);
}
save_item(NAME(m_intvkbd_text_blanked));
save_item(NAME(m_intvkbd_keyboard_col));
save_item(NAME(m_tape_int_pending));
save_item(NAME(m_tape_interrupts_enabled));
save_item(NAME(m_tape_motor_mode));
}
if (m_cart && m_cart->exists())
{
// RAM
switch (m_cart->get_type())
{
case INTV_RAM:
m_maincpu->space(AS_PROGRAM).install_readwrite_handler(0xd000, 0xd7ff, read16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::read_ram)), write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_ram)));
break;
case INTV_GFACT:
m_maincpu->space(AS_PROGRAM).install_readwrite_handler(0x8800, 0x8fff, read16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::read_ram)), write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_ram)));
break;
case INTV_VOICE:
m_cart->late_subslot_setup();
m_maincpu->space(AS_PROGRAM).install_readwrite_handler(0x0080, 0x0081, read16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::read_speech)), write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_speech)));
// passthru for RAM-equipped carts
m_maincpu->space(AS_PROGRAM).install_write_handler(0x8800, 0x8fff, write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_88)));
m_maincpu->space(AS_PROGRAM).install_write_handler(0xd000, 0xd7ff, write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_d0)));
break;
case INTV_ECS:
m_cart->late_subslot_setup();
m_maincpu->space(AS_PROGRAM).install_readwrite_handler(0x00f0, 0x00ff, read16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::read_ay)), write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_ay)));
m_maincpu->space(AS_PROGRAM).install_readwrite_handler(0x4000, 0x47ff, read16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::read_ram)), write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_ram)));
m_maincpu->space(AS_PROGRAM).install_write_handler(0x2000, 0x2fff, write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_rom20)));
m_maincpu->space(AS_PROGRAM).install_write_handler(0x7000, 0x7fff, write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_rom70)));
m_maincpu->space(AS_PROGRAM).install_write_handler(0xe000, 0xefff, write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_rome0)));
m_maincpu->space(AS_PROGRAM).install_write_handler(0xf000, 0xffff, write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_romf0)));
// passthru for Intellivoice expansion
m_maincpu->space(AS_PROGRAM).install_readwrite_handler(0x0080, 0x0081, read16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::read_speech)), write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_speech)));
// passthru for RAM-equipped carts
m_maincpu->space(AS_PROGRAM).install_write_handler(0x8800, 0x8fff, write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_88)));
m_maincpu->space(AS_PROGRAM).install_write_handler(0xd000, 0xd7ff, write16sm_delegate(*m_cart, FUNC(intv_cart_slot_device::write_d0)));
break;
}
m_cart->save_ram();
}
}
TIMER_CALLBACK_MEMBER(intv_state::intv_interrupt_complete)
{
m_maincpu->set_input_line(CP1610_INT_INTRM, CLEAR_LINE);
m_bus_copy_mode = 0;
}
TIMER_CALLBACK_MEMBER(intv_state::intv_btb_fill)
{
uint8_t row = m_backtab_row;
//m_maincpu->adjust_icount(-STIC_ROW_FETCH);
for (int column = 0; column < stic_device::BACKTAB_WIDTH; column++)
m_stic->write_to_btb(row, column, m_ram16[column + row * stic_device::BACKTAB_WIDTH]);
m_backtab_row += 1;
}
INTERRUPT_GEN_MEMBER(intv_state::intv_interrupt)
{
int delay = m_stic->read_row_delay();
m_maincpu->set_input_line(CP1610_INT_INTRM, ASSERT_LINE);
m_sr1_int_pending = 1;
m_bus_copy_mode = 1;
m_backtab_row = 0;
m_maincpu->adjust_icount(-(12*stic_device::ROW_BUSRQ+stic_device::FRAME_BUSRQ)); // Account for stic cycle stealing
timer_set(m_maincpu->cycles_to_attotime(stic_device::VBLANK_END), TIMER_INTV_INTERRUPT_COMPLETE);
for (int row = 0; row < stic_device::BACKTAB_HEIGHT; row++)
{
timer_set(m_maincpu->cycles_to_attotime(stic_device::FIRST_FETCH-stic_device::FRAME_BUSRQ+stic_device::CYCLES_PER_SCANLINE*stic_device::Y_SCALE*delay + (stic_device::CYCLES_PER_SCANLINE*stic_device::Y_SCALE*stic_device::CARD_HEIGHT - stic_device::ROW_BUSRQ)*row), TIMER_INTV_BTB_FILL);
}
if (delay == 0)
{
m_maincpu->adjust_icount(-stic_device::ROW_BUSRQ); // extra row fetch occurs if vertical delay == 0
}
m_stic->screenrefresh();
}
#if defined(LATER)
INTERRUPT_GEN( intvkbd_interrupt2 )
{
static int tape_interrupt_divider = 0;
tape_interrupt_divider++;
tape_interrupt_divider = tape_interrupt_divider % 50;
if (tape_interrupt_divider == 0)
{
#if 0
update_tape_drive();
/* do sr1 interrupt plus possible tape interrupt */
if (tape_interrupts_enabled)
tape_int_pending = 1;
#endif
sr1_int_pending = 1;
cpu_set_irq_line(1, 0, PULSE_LINE);
}
else
{
update_tape_drive();
/* do only possible tape interrupt */
if (tape_interrupts_enabled)
{
tape_int_pending = 1;
cpu_set_irq_line(1, 0, PULSE_LINE);
}
}
}
#endif