// license:BSD-3-Clause // copyright-holders:Curt Coder /********************************************************************** Motorola MCCS1850 Serial Real-Time Clock emulation *********************************************************************/ /* TODO: - auto restart - test mode */ #include "emu.h" #include "mccs1850.h" //#define VERBOSE 0 #include "logmacro.h" //************************************************************************** // MACROS / CONSTANTS //************************************************************************** #define RAM_SIZE 0x80 // serial state enum { STATE_ADDRESS, STATE_DATA }; // registers enum { REGISTER_COUNTER_LATCH = 0x20, REGISTER_ALARM_LATCH = 0x24, REGISTER_STATUS = 0x30, REGISTER_CONTROL = 0x31, REGISTER_TEST_1 = 0x3e, REGISTER_TEST_2 = 0x3f, REGISTER_TEST_KICK_START_COUNTER = 0x40, REGISTER_TEST_PRESCALE_COUNTER = 0x43, REGISTER_TEST_COUNTER_INCREMENT = 0x4f }; // clock status/interrupt register #define STATUS_TM 0x20 // test mode #define STATUS_FTU 0x10 // first time up #define STATUS_IT 0x08 // interrupt true #define STATUS_LB 0x04 // low battery #define STATUS_AI 0x02 // alarm #define STATUS_RPD 0x01 // request to power down // clock control register #define CONTROL_STR_STP 0x80 // start/stop #define CONTROL_PD 0x40 // power down #define CONTROL_AR 0x20 // auto restart #define CONTROL_AE 0x10 // alarm enable #define CONTROL_AC 0x08 // alarm clear #define CONTROL_FTUC 0x04 // first time up clear #define CONTROL_LBE 0x02 // low battery enable #define CONTROL_RPCD 0x01 // request to power down clear // test register 1 #define TEST1_DIV1 0x80 // divide by 1 #define TEST1_VOVR 0x40 // Vdd override #define TEST1_VDDUP 0x20 // Vdd up #define TEST1_VDDON 0x10 // Vdd on #define TEST1_VRT 0x08 // valid RAM and time #define TEST1_LOW_BAT 0x08 // low battery #define TEST1_PCC 0x04 // programmable capacitor C (10.0 pF) #define TEST1_PCB 0x02 // programmable capacitor B (5.0 pF) #define TEST1_PCA 0x01 // programmable capacitor A (2.5 pF) // test register 2 #define TEST2_OSCBY 0x80 // oscillator bypass #define TEST2_COMPOVR 0x40 // comparator override #define TEST2_POR 0x20 // power on reset #define TEST2_SELTCK 0x10 // select test clock #define TEST2_FRZ 0x08 // freeze mode #define TEST2_DV_MASK 0x07 // divider bits select //************************************************************************** // GLOBAL VARIABLES //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(MCCS1850, mccs1850_device, "mccs1850", "MCCS1850 RTC") //************************************************************************** // INLINE HELPERS //************************************************************************** //------------------------------------------------- // check_interrupt - //------------------------------------------------- inline void mccs1850_device::check_interrupt() { uint8_t status = m_ram[REGISTER_STATUS]; uint8_t control = m_ram[REGISTER_CONTROL]; bool interrupt = (((status & STATUS_AI) && (control & CONTROL_AE)) // alarm interrupt || ((status & STATUS_LB) && (control & CONTROL_LBE)) // low battery || (status & STATUS_FTU) // first time up || (status & STATUS_RPD)); // request to power down if (interrupt) { m_ram[REGISTER_STATUS] |= STATUS_IT; } else { m_ram[REGISTER_STATUS] &= ~STATUS_IT; } if(!int_cb.isnull()) int_cb(interrupt); } //------------------------------------------------- // set_pse_line - //------------------------------------------------- inline void mccs1850_device::set_pse_line(bool state) { m_pse = state; if(!pse_cb.isnull()) pse_cb(m_pse); } //------------------------------------------------- // read_register - //------------------------------------------------- inline uint8_t mccs1850_device::read_register(offs_t offset) { switch (offset) { case REGISTER_COUNTER_LATCH: case REGISTER_COUNTER_LATCH+3: // Required by the NeXT power on test // load counter value into latch m_ram[REGISTER_COUNTER_LATCH] = m_counter >> 24; m_ram[REGISTER_COUNTER_LATCH + 1] = m_counter >> 16; m_ram[REGISTER_COUNTER_LATCH + 2] = m_counter >> 8; m_ram[REGISTER_COUNTER_LATCH + 3] = m_counter; break; case REGISTER_TEST_1: case REGISTER_TEST_2: case REGISTER_TEST_KICK_START_COUNTER: case REGISTER_TEST_PRESCALE_COUNTER: case REGISTER_TEST_COUNTER_INCREMENT: logerror("MCCS1850 Unsupported read from test register %02x!\n", offset); break; } return m_ram[offset]; } //------------------------------------------------- // write_register - //------------------------------------------------- inline void mccs1850_device::write_register(offs_t offset, uint8_t data) { switch (offset) { case REGISTER_STATUS: // read only break; case REGISTER_CONTROL: LOG("MCCS1850 Counter %s\n", (data & CONTROL_STR_STP) ? "Start" : "Stop"); m_clock_timer->enable(data & CONTROL_STR_STP); if (data & CONTROL_PD) { LOG("MCCS1850 Power Down\n"); set_pse_line(false); } if (data & CONTROL_AR) { LOG("MCCS1850 Auto Restart\n"); } if (data & CONTROL_AC) { LOG("MCCS1850 Alarm Clear\n"); m_ram[REGISTER_STATUS] &= ~STATUS_AI; } if (data & CONTROL_FTUC) { LOG("MCCS1850 First Time Up Clear\n"); m_ram[REGISTER_STATUS] &= ~STATUS_FTU; } if (data & CONTROL_RPCD) { LOG("MCCS1850 Request to Power Down Clear\n"); m_ram[REGISTER_STATUS] &= ~STATUS_RPD; } m_ram[REGISTER_CONTROL] = data & 0xb2; check_interrupt(); break; case REGISTER_TEST_1: case REGISTER_TEST_2: case REGISTER_TEST_KICK_START_COUNTER: case REGISTER_TEST_PRESCALE_COUNTER: case REGISTER_TEST_COUNTER_INCREMENT: logerror("MCCS1850 Unsupported write to test register %02x!\n", offset); break; default: m_ram[offset] = data; } } //------------------------------------------------- // advance_seconds - //------------------------------------------------- inline void mccs1850_device::advance_seconds() { uint32_t alarm = (m_ram[REGISTER_ALARM_LATCH] << 24) | (m_ram[REGISTER_ALARM_LATCH + 1] << 16) | (m_ram[REGISTER_ALARM_LATCH + 2] << 8) | m_ram[REGISTER_ALARM_LATCH + 3]; m_counter++; if (m_counter == alarm) { if (m_pse) { // trigger alarm m_ram[REGISTER_STATUS] |= STATUS_AI; check_interrupt(); } else { // wake up set_pse_line(true); } } } //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // mccs1850_device - constructor //------------------------------------------------- mccs1850_device::mccs1850_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, MCCS1850, tag, owner, clock), device_rtc_interface(mconfig, *this), device_nvram_interface(mconfig, *this), int_cb(*this), pse_cb(*this), nuc_cb(*this), m_pse(1), m_counter(0), m_ce(0), m_sck(0), m_sdo(1), m_sdi(0), m_state(STATE_ADDRESS), m_bits(0) { } //------------------------------------------------- // rtc_clock_changed - //------------------------------------------------- void mccs1850_device::rtc_clock_updated(int year, int month, int day, int day_of_week, int hour, int minute, int second) { // FIXME: implement this properly system_time systime; machine().base_datetime(systime); m_counter = systime.time; } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void mccs1850_device::device_start() { // resolve callbacks int_cb.resolve(); pse_cb.resolve(); nuc_cb.resolve(); // allocate timers m_clock_timer = timer_alloc(TIMER_CLOCK); m_clock_timer->adjust(attotime::from_hz(clock() / 32768), 0, attotime::from_hz(clock() / 32768)); // state saving save_item(NAME(m_pse)); save_item(NAME(m_counter)); save_item(NAME(m_ce)); save_item(NAME(m_sck)); save_item(NAME(m_sdo)); save_item(NAME(m_sdi)); save_item(NAME(m_state)); save_item(NAME(m_address)); save_item(NAME(m_bits)); save_item(NAME(m_shift)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void mccs1850_device::device_reset() { if(!m_counter) m_ram[REGISTER_STATUS] = 0x80 | STATUS_FTU; else m_ram[REGISTER_STATUS] = 0x80; m_ram[REGISTER_CONTROL] = 0x00; } //------------------------------------------------- // device_timer - handler timer events //------------------------------------------------- void mccs1850_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case TIMER_CLOCK: advance_seconds(); break; } } //------------------------------------------------- // nvram_default - called to initialize NVRAM to // its default state //------------------------------------------------- void mccs1850_device::nvram_default() { memset(m_ram, 0xff, RAM_SIZE); if (machine().root_device().memregion(tag()) != nullptr) { uint8_t *nvram = machine().root_device().memregion(tag())->base(); // initialize NVRAM memcpy(m_ram, nvram, 0x20); } } //------------------------------------------------- // nvram_read - called to read NVRAM from the // .nv file //------------------------------------------------- void mccs1850_device::nvram_read(emu_file &file) { file.read(m_ram, RAM_SIZE); } //------------------------------------------------- // nvram_write - called to write NVRAM to the // .nv file //------------------------------------------------- void mccs1850_device::nvram_write(emu_file &file) { file.write(m_ram, RAM_SIZE); } //------------------------------------------------- // ce_w - chip enable write //------------------------------------------------- WRITE_LINE_MEMBER( mccs1850_device::ce_w ) { m_ce = state; if (!m_ce) { m_state = STATE_ADDRESS; m_bits = 0; } } //------------------------------------------------- // sck_w - serial clock write //------------------------------------------------- WRITE_LINE_MEMBER( mccs1850_device::sck_w ) { if (!m_ce) return; switch (m_state) { case STATE_ADDRESS: if (m_sck && !state) { m_address <<= 1; m_address |= m_sdi; m_bits++; if (m_bits == 8) { LOG("MCCS1850 %s Address %02x\n", BIT(m_address, 7) ? "Write" : "Read", m_address & 0x7f); m_bits = 0; m_state = STATE_DATA; if (!BIT(m_address, 7)) { m_shift = read_register(m_address & 0x7f); LOG("MCCS1850 Data Out %02x\n", m_shift); } } } break; case STATE_DATA: if (BIT(m_address, 7) && m_sck && !state) { // shift data in m_shift <<= 1; m_shift |= m_sdi; m_bits++; if (m_bits == 8) { LOG("MCCS1850 Data In %02x\n", m_shift); write_register(m_address & 0x7f, m_shift); m_bits = 0; // increment address counter m_address++; m_address |= 0x80; } } else if (!BIT(m_address, 7) && !m_sck && state) { // shift data out m_sdo = BIT(m_shift, 7); m_shift <<= 1; m_bits++; if (m_bits == 8) { m_bits = 0; // increment address counter m_address++; m_address &= 0x7f; m_shift = read_register(m_address & 0x7f); LOG("MCCS1850 Data Out %02x\n", m_shift); } } break; } m_sck = state; } //------------------------------------------------- // sdo_r - serial data out read //------------------------------------------------- READ_LINE_MEMBER( mccs1850_device::sdo_r ) { if (!m_ce || BIT(m_address, 7)) { // Hi-Z return 1; } else { return m_sdo; } } //------------------------------------------------- // sdi_w - serial data in write //------------------------------------------------- WRITE_LINE_MEMBER( mccs1850_device::sdi_w ) { m_sdi = state; } //------------------------------------------------- // pwrsw_w - power switch write //------------------------------------------------- WRITE_LINE_MEMBER( mccs1850_device::pwrsw_w ) { if (!state) { if (m_pse) { // request to power down m_ram[REGISTER_STATUS] |= STATUS_RPD; check_interrupt(); } set_pse_line(true); } } //------------------------------------------------- // por_w - power on reset write //------------------------------------------------- WRITE_LINE_MEMBER( mccs1850_device::por_w ) { if (!state) { device_reset(); } } //------------------------------------------------- // test_w - test mode write //------------------------------------------------- WRITE_LINE_MEMBER( mccs1850_device::test_w ) { if (state) { LOG("MCCS1850 Test Mode\n"); m_ram[REGISTER_STATUS] |= STATUS_TM; } else { LOG("MCCS1850 Normal Operation\n"); m_ram[REGISTER_STATUS] &= ~STATUS_TM; } }