// license:BSD-3-Clause // copyright-holders:Robbbert /********************************************************************** i8279 2012-JAN-08 First draft [Robbbert] 2012-JAN-12 Implemented Notes: - All keys MUST be ACTIVE_LOW ToDo: - Command 5 (Nibble masking and blanking) - Command 7 (Error Mode) - Interrupts - BD pin - Sensor ram stuff - save state What has been done: CMD 0: - Display Mode -- Left & Right with no increment are the same thing -- Right with increment is not emulated yet *** - Keyboard Mode -- No particular code has been added for 2-key/N-key rollover, no need -- Sensor mode is not complete yet *** -- Encoded and Decoded are done -- Strobe is done -- Sensor and FIFO may share the same internal RAM, not sure CMD 1: - Clock Divider -- Value is stored, but internally a fixed value is always used CMD 2: - Read FIFO/Sensor RAM -- FIFO works -- Sensor RAM works CMD 3: - Read Display RAM -- This works CMD 4: - Write Display RAM -- Right with increment does nothing, the rest is working *** CMD 5: - Blank Nibble -- Not done *** - Mask Nibble -- Implemented CMD 6: -- All implemented CMD 7: - Interrupt -- Not done - Error Mode -- No need to do. Interface: -- All done except BD pin *** Status word: - FIFO bits -- All done - Error bit -- Not done (no need) - Display unavailable -- Not done (no need) Items marked (***) can be added if a system appears that uses this feature. **********************************************************************/ #include "emu.h" #include "i8279.h" //#define VERBOSE 1 #include "logmacro.h" //************************************************************************** // LIVE DEVICE //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(I8279, i8279_device, "i8279", "Intel 8279 KDC") //------------------------------------------------- // i8279_device - constructor //------------------------------------------------- i8279_device::i8279_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, I8279, tag, owner, clock), m_out_irq_cb(*this), m_out_sl_cb(*this), m_out_disp_cb(*this), m_out_bd_cb(*this), m_in_rl_cb(*this), m_in_shift_cb(*this), m_in_ctrl_cb(*this) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void i8279_device::device_start() { /* resolve callbacks */ m_out_irq_cb.resolve(); m_out_sl_cb.resolve(); m_out_disp_cb.resolve(); m_out_bd_cb.resolve(); m_in_rl_cb.resolve(); m_in_shift_cb.resolve(); m_in_ctrl_cb.resolve(); m_clock = clock(); m_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(i8279_device::timerproc_callback), this)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void i8279_device::device_reset() { uint8_t i; // startup values are unknown: setting to 0 for (i = 2; i < 8; i++) m_cmd[i] = 0; for (i = 0; i < 8; i++) m_fifo[i] = 0; for (i = 0; i < 8; i++) m_s_ram[i] = 0; for (i = 0; i < 16; i++) m_d_ram[i] = 0; m_status = 0; m_autoinc = true; m_d_ram_ptr = 0; m_s_ram_ptr = 0; m_read_flag = 0; m_scanner = 0; m_ctrl_key = 1; m_key_down = 0xffff; // from here is confirmed m_cmd[0] = 8; m_cmd[1] = 31; logerror("Initial clock = 3100kHz\n"); timer_adjust(); } void i8279_device::timer_adjust() { // Real device runs at about 100kHz internally, clock divider is chosen so that // this is the case. We do not need such speed, 2000Hz is enough. // If this is too long, the sensor mode doesn't work correctly. #if 0 uint8_t divider = (m_cmd[1]) ? m_cmd[1] : 1; uint32_t new_clock = clock() / divider; #else uint32_t new_clock = 2000; #endif if (m_clock != new_clock) { m_timer->adjust(attotime::from_hz(new_clock), 0, attotime::from_hz(new_clock)); m_clock = new_clock; } } void i8279_device::clear_display() { // clear all digits uint8_t i,patterns[4] = { 0, 0, 0x20, 0xff }; uint8_t data = patterns[(m_cmd[6] & 12) >> 2]; // The CD high bit (also done by CA) if (m_cmd[6] & 0x11) for (i = 0; i < 16; i++) m_d_ram[i] = data; m_status &= 0x7f; // bit 7 not emulated, but do it anyway m_d_ram_ptr = 0; // not in the datasheet, but needed // The CF bit (also done by CA) if (m_cmd[6] & 3) { m_status &= 0xc0; // blow away fifo m_s_ram_ptr = 0; // reset sensor pointer set_irq(0); // reset irq } } void i8279_device::set_irq(bool state) { if ( !m_out_irq_cb.isnull() ) m_out_irq_cb( state ); } void i8279_device::new_key(uint8_t data, bool skey, bool ckey) { uint8_t i, rl, sl; for (i = 0; BIT(data, i); i++) {}; rl = i; if (BIT(m_cmd[0], 0)) { for (i = 0; !BIT(data, i); i++) {}; sl = i; } else sl = m_scanner; new_fifo( (ckey << 7) | (skey << 6) | (sl << 3) | rl); } void i8279_device::new_fifo(uint8_t data) { // see if already overrun if (BIT(m_status, 5)) return; // set overrun flag if full if (BIT(m_status, 3)) { m_status |= 0x20; return; } m_fifo[m_status & 7] = data; // bump fifo size & turn off underrun uint8_t fifo_size = m_status & 7; if ((fifo_size)==7) m_status |= 8; // full else m_status = (m_status & 0xe8) + fifo_size + 1; if (!fifo_size) set_irq(1); // something just went into fifo, so int } TIMER_CALLBACK_MEMBER( i8279_device::timerproc_callback ) { timer_mainloop(); } void i8279_device::timer_mainloop() { // control byte 0 // bit 0 - encoded or decoded keyboard scan // bits 1,2 - keyboard type // bit 3 - number of digits to display // bit 4 - left or right entry uint8_t scanner_mask = BIT(m_cmd[0], 0) ? 15 : BIT(m_cmd[0], 3) ? 15 : 7; bool decoded = BIT(m_cmd[0], 0); uint8_t kbd_type = (m_cmd[0] & 6) >> 1; bool shift_key = 1; bool ctrl_key = 1; bool strobe_pulse = 0; // keyboard // type 0 = kbd, 2-key lockout // type 1 = kdb, n-key // type 2 = sensor // type 3 = strobed // Get shift keys if ( !m_in_shift_cb.isnull() ) shift_key = m_in_shift_cb(); if ( !m_in_ctrl_cb.isnull() ) ctrl_key = m_in_ctrl_cb(); if (ctrl_key && !m_ctrl_key) strobe_pulse = 1; // low-to-high is a strobe m_ctrl_key = ctrl_key; // Read a row of keys if ( !m_in_rl_cb.isnull() ) { uint8_t rl = m_in_rl_cb(0); // see if key still down from last time uint16_t key_down = (m_scanner << 8) | rl; if (key_down == m_key_down) rl = 0xff; else if ((rl == 0xff) && (m_scanner == m_key_down >> 8)) m_key_down = 0xffff; // now process new key if (rl < 0xff || kbd_type == 2) { m_key_down = key_down; switch (kbd_type) { case 0: case 1: new_key(rl, shift_key, ctrl_key); break; case 2: { uint8_t addr = m_scanner &7; if (decoded) for (addr=0; !BIT(m_scanner, addr); addr++) {}; rl ^= 0xff; // inverted assert(addr < ARRAY_LENGTH(m_s_ram)); if (m_s_ram[addr] != rl) { m_s_ram[addr] = rl; // IRQ line goes high if a row change value set_irq(1); } } break; case 3: if (strobe_pulse) new_fifo(rl); break; } } } // Increment scanline if (decoded) { m_scanner<<= 1; if ((m_scanner & 15)==0) m_scanner = 1; } else m_scanner++; m_scanner &= scanner_mask; // 4-bit port if ( !m_out_sl_cb.isnull() ) m_out_sl_cb((offs_t)0, m_scanner); // output a digit if ( !m_out_disp_cb.isnull() ) m_out_disp_cb((offs_t)0, m_d_ram[m_scanner] ); } READ8_MEMBER(i8279_device::read) { // A0 = control/data select return (offset & 1) ? status_r(space, 0) : data_r(space, 0); } READ8_MEMBER( i8279_device::status_r ) { return m_status; } READ8_MEMBER( i8279_device::data_r ) { uint8_t i; bool sensor_mode = ((m_cmd[0] & 6)==4); uint8_t data; if (m_read_flag) { // read the display ram data = m_d_ram[m_d_ram_ptr]; if (m_autoinc) { m_d_ram_ptr++; } } else if (sensor_mode) { // read sensor ram assert(m_s_ram_ptr < ARRAY_LENGTH(m_s_ram)); data = m_s_ram[m_s_ram_ptr]; if (m_autoinc) { m_s_ram_ptr++; } else { set_irq(0); } } else { // read a key from fifo data = m_fifo[0]; uint8_t fifo_size = m_status & 7; switch (m_status & 0x38) { case 0x00: // no errors if (!fifo_size) m_status |= 0x10; // underrun else { for (i = 1; i < 8; i++) m_fifo[i-1] = m_fifo[i]; fifo_size--; if (!fifo_size) set_irq(0); } break; case 0x28: // overrun case 0x08: // fifo full for (i = 1; i < 8; i++) m_fifo[i-1] = m_fifo[i]; break; case 0x10: // underrun if (!fifo_size) break; default: printf("Invalid status: %X\n", m_status); } m_status = (m_status & 0xd0) | fifo_size; // turn off overrun & full } m_d_ram_ptr &= 15; m_s_ram_ptr &= 7; return data; } WRITE8_MEMBER(i8279_device::write) { // A0 = control/data select if (offset & 1) cmd_w(space, 0, data); else data_w(space, 0, data); } WRITE8_MEMBER( i8279_device::cmd_w ) {//printf("Command: %X=%X ",data>>5,data&31); uint8_t cmd = data >> 5; data &= 0x1f; m_cmd[cmd] = data; switch (cmd) { case 0: LOG("I8279 kb mode %x, display mode %x\n", data & 7, (data>>3) & 3); break; case 1: if (data > 1) { logerror("Clock set to %dkHz\n",data*100); timer_adjust(); } break; case 2: m_read_flag = 0; if ((m_cmd[0] & 6)==4) // sensor mode only { m_autoinc = BIT(data, 4); m_s_ram_ptr = data & 7; LOG("I8279 selct sensor row %x, AI %d\n", m_s_ram_ptr, m_autoinc); } break; case 3: m_read_flag = 1; m_d_ram_ptr = data & 15; m_autoinc = BIT(data, 4); break; case 4: m_d_ram_ptr = data & 15; m_autoinc = BIT(data, 4); break; case 6: LOG("I8279 clear cmd %x\n", data); clear_display(); break; } } WRITE8_MEMBER( i8279_device::data_w ) {//printf("Data: %X ",data); if (BIT(m_cmd[0], 4) && m_autoinc) { // right-entry autoincrement not implemented yet } else { if (!(m_cmd[5] & 0x04)) m_d_ram[m_d_ram_ptr] = (m_d_ram[m_d_ram_ptr] & 0xf0) | (data & 0x0f); if (!(m_cmd[5] & 0x08)) m_d_ram[m_d_ram_ptr] = (m_d_ram[m_d_ram_ptr] & 0x0f) | (data & 0xf0); if (m_autoinc) m_d_ram_ptr++; } m_d_ram_ptr &= 15; }