/* 68681 DUART Written by Mariusz Wojcieszek */ #include "driver.h" #include "68681.h" #define VERBOSE 0 #define LOG(x) do { if (VERBOSE) logerror x; } while (0) static const char *const duart68681_reg_read_names[0x10] = { "MRA", "SRA", "BRG Test", "RHRA", "IPCR", "ISR", "CTU", "CTL", "MRB", "SRB", "1X/16X Test", "RHRB", "IVR", "Input Ports", "Start Counter", "Stop Counter" }; static const char *const duart68681_reg_write_names[0x10] = { "MRA", "CSRA", "CRA", "THRA", "ACR", "IMR", "CRUR", "CTLR", "MRB", "CSRB", "CRB", "THRB", "IVR", "OPCR", "Set OP Bits", "Reset OP Bits" }; #define INT_INPUT_PORT_CHANGE 0x80 #define INT_DELTA_BREAK_B 0x40 #define INT_RXRDY_FFULLB 0x20 #define INT_TXRDYB 0x10 #define INT_COUNTER_READY 0x08 #define INT_DELTA_BREAK_A 0x04 #define INT_RXRDY_FFULLA 0x02 #define INT_TXRDYA 0x01 #define STATUS_RECEIVED_BREAK 0x80 #define STATUS_FRAMING_ERROR 0x40 #define STATUS_PARITY_ERROR 0x20 #define STATUS_OVERRUN_ERROR 0x10 #define STATUS_TRANSMITTER_EMPTY 0x08 #define STATUS_TRANSMITTER_READY 0x04 #define STATUS_FIFO_FULL 0x02 #define STATUS_RECEIVER_READY 0x01 #define MODE_RX_INT_SELECT_BIT 6 #define RX_FIFO_SIZE 3 typedef struct { /* Registers */ UINT8 CR; /* Command register */ UINT8 CSR; /* Clock select register */ UINT8 MR1; /* Mode register 1 */ UINT8 MR2; /* Mode register 2 */ UINT8 MR_ptr; /* Mode register pointer */ UINT8 SR; /* Status register */ /* State */ int baud_rate; /* Receiver */ UINT8 rx_enabled; UINT8 rx_fifo[RX_FIFO_SIZE]; int rx_fifo_read_ptr; int rx_fifo_write_ptr; int rx_fifo_num; /* Transmitter */ UINT8 tx_enabled; UINT8 tx_data; UINT8 tx_ready; emu_timer *tx_timer; } DUART68681_CHANNEL; typedef struct { /* device */ const device_config *device; /* config */ const duart68681_config *duart_config; /* registers */ UINT8 ACR; /* Auxiliary Control Register */ UINT8 IMR; /* Interrupt Mask Register */ UINT8 ISR; /* Interrupt Status Register */ UINT8 IVR; /* Interrupt Vector Register */ UINT8 OPCR; /* Output Port Conf. Register */ UINT8 OPR; /* Output Port Register */ PAIR CTR; /* Counter/Timer Preset Value */ /* state */ UINT8 IP_last_state; /* last state of IP bits */ /* timer */ emu_timer *duart_timer; /* UART channels */ DUART68681_CHANNEL channel[2]; } duart68681_state; INLINE duart68681_state *get_safe_token(const device_config *device) { assert(device != NULL); assert(device->token != NULL); assert(device->type == DUART68681); return (duart68681_state *)device->token; } static void duart68681_update_interrupts(duart68681_state *duart68681) { if ( (duart68681->ISR & duart68681->IMR) != 0 ) { if ( duart68681->duart_config->irq_handler ) { duart68681->duart_config->irq_handler( duart68681->device, duart68681->IVR ); } } }; static TIMER_CALLBACK( duart_timer_callback ) { const device_config *device = ptr; duart68681_state *duart68681 = get_safe_token(device); duart68681->ISR |= INT_COUNTER_READY; duart68681_update_interrupts(duart68681); }; static void duart68681_write_MR(duart68681_state *duart68681, int ch, UINT8 data) { if ( duart68681->channel[ch].MR_ptr == 0 ) { duart68681->channel[ch].MR1 = data; duart68681->channel[ch].MR_ptr = 1; } else { duart68681->channel[ch].MR2 = data; } }; static void duart68681_write_CSR(duart68681_state *duart68681, int ch, UINT8 data, UINT8 ACR) { static const int baud_rate_ACR_0[] = { 50, 110, 134, 200, 300, 600, 1200, 1050, 2400, 4800, 7200, 9600, 38400, 0, 0, 0 }; static const int baud_rate_ACR_1[] = { 75, 110, 134, 150, 300, 600, 1200, 2000, 2400, 4800, 1800, 9600, 19200, 0, 0, 0 }; duart68681->channel[ch].CSR = data; if ( BIT(ACR,7) == 0 ) { duart68681->channel[ch].baud_rate = baud_rate_ACR_0[data & 0x0f]; } else { duart68681->channel[ch].baud_rate = baud_rate_ACR_1[data & 0x0f]; } if ( duart68681->channel[ch].baud_rate == 0 ) { LOG(( "Unsupported transmitter clock: channel %d, clock select = %02x\n", ch, data )); } }; static void duart68681_write_CR(duart68681_state *duart68681, int ch, UINT8 data) { duart68681->channel[ch].CR = data; if ( BIT(data,0) ) { duart68681->channel[ch].rx_enabled = 1; } if ( BIT(data,1) ) { duart68681->channel[ch].rx_enabled = 0; duart68681->channel[ch].SR &= ~STATUS_RECEIVER_READY; } if ( BIT(data,2) ) { duart68681->channel[ch].tx_enabled = 1; duart68681->channel[ch].tx_ready = 1; duart68681->channel[ch].SR |= STATUS_TRANSMITTER_READY; } if ( BIT(data,3) ) { duart68681->channel[ch].tx_enabled = 0; duart68681->channel[ch].tx_ready = 0; duart68681->channel[ch].SR &= ~STATUS_TRANSMITTER_READY; } switch( (data >> 4) & 0x07 ) { case 0: /* No command */ break; case 1: /* Reset MR pointer. Causes the Channel A MR pointer to point to MR1 */ duart68681->channel[ch].MR_ptr = 0; break; case 2: /* Reset channel A receiver (disable receiver and flush fifo) */ duart68681->channel[ch].rx_enabled = 0; duart68681->channel[ch].SR &= ~STATUS_RECEIVER_READY; duart68681->channel[ch].rx_fifo_read_ptr = 0; duart68681->channel[ch].rx_fifo_write_ptr = 0; duart68681->channel[ch].rx_fifo_num = 0; break; case 3: /* Reset channel A transmitter */ duart68681->channel[ch].tx_enabled = 0; duart68681->channel[ch].SR &= ~STATUS_TRANSMITTER_READY; timer_adjust_oneshot(duart68681->channel[ch].tx_timer, attotime_never, ch); break; case 4: /* Reset Error Status */ duart68681->channel[ch].SR &= ~(STATUS_RECEIVED_BREAK | STATUS_FRAMING_ERROR | STATUS_PARITY_ERROR | STATUS_OVERRUN_ERROR); break; case 5: /* Reset Channel break change interrupt */ if ( ch == 0 ) { duart68681->ISR &= ~INT_DELTA_BREAK_A; } else { duart68681->ISR &= ~INT_DELTA_BREAK_B; } duart68681_update_interrupts(duart68681); break; default: LOG(( "68681: Unhandled command (%x) in CR%d\n", (data >> 4) & 0x07, ch )); break; } }; static UINT8 duart68681_read_rx_fifo(duart68681_state *duart68681, int ch) { UINT8 r; if ( duart68681->channel[ch].rx_fifo_num == 0 ) { LOG(( "68681: rx fifo underflow\n" )); return 0x0; } r = duart68681->channel[ch].rx_fifo[duart68681->channel[ch].rx_fifo_read_ptr++]; if ( duart68681->channel[ch].rx_fifo_read_ptr == RX_FIFO_SIZE ) { duart68681->channel[ch].rx_fifo_read_ptr = 0; } duart68681->channel[ch].rx_fifo_num--; if ( duart68681->channel[ch].rx_fifo_num > 0 ) { duart68681->channel[ch].SR |= STATUS_RECEIVER_READY; if ( ch == 0 ) { duart68681->ISR |= INT_RXRDY_FFULLA; } else { duart68681->ISR |= INT_RXRDY_FFULLB; } } else { duart68681->channel[ch].SR &= ~STATUS_RECEIVER_READY; if ( ch == 0 ) { duart68681->ISR &= ~INT_RXRDY_FFULLA; } else { duart68681->ISR &= ~INT_RXRDY_FFULLB; } } duart68681_update_interrupts(duart68681); return r; }; static TIMER_CALLBACK( tx_timer_callback ) { const device_config *device = ptr; duart68681_state *duart68681 = get_safe_token(device); int ch = param & 1; if (duart68681->duart_config->tx_callback) duart68681->duart_config->tx_callback(device, ch, duart68681->channel[ch].tx_data); duart68681->channel[ch].tx_ready = 1; duart68681->channel[ch].SR |= STATUS_TRANSMITTER_READY; if (ch == 0) duart68681->ISR |= INT_TXRDYA; else duart68681->ISR |= INT_TXRDYB; duart68681_update_interrupts(duart68681); timer_adjust_oneshot(duart68681->channel[ch].tx_timer, attotime_never, ch); }; static void duart68681_write_TX(duart68681_state* duart68681, int ch, UINT8 data) { attotime period; duart68681->channel[ch].tx_data = data; duart68681->channel[ch].tx_ready = 0; duart68681->channel[ch].SR &= ~STATUS_TRANSMITTER_READY; if (ch == 0) duart68681->ISR &= ~INT_TXRDYA; else duart68681->ISR &= ~INT_TXRDYB; duart68681_update_interrupts(duart68681); period = ATTOTIME_IN_HZ(duart68681->channel[ch].baud_rate / 10 ); timer_adjust_oneshot(duart68681->channel[ch].tx_timer, period, ch); }; READ8_DEVICE_HANDLER(duart68681_r) { duart68681_state* duart68681 = get_safe_token(device); UINT8 r = 0xff; offset &= 0xf; LOG(( "Reading 68681 (%s) reg %x (%s) ", device->tag, offset, duart68681_reg_read_names[offset] )); switch (offset) { case 0x00: /* MR1A/MR2A */ if ( duart68681->channel[0].MR_ptr == 0 ) { r = duart68681->channel[0].MR1; duart68681->channel[0].MR_ptr = 1; } else { r = duart68681->channel[0].MR2; } break; case 0x01: /* SRA */ r = duart68681->channel[0].SR; break; case 0x03: /* Rx Holding Register A */ r = duart68681_read_rx_fifo(duart68681, 0); break; case 0x04: /* IPCR */ { UINT8 IP; if ( duart68681->duart_config->input_port_read != NULL ) IP = duart68681->duart_config->input_port_read(duart68681->device); else IP = 0x0; r = (((duart68681->IP_last_state ^ IP) & 0x0f) << 4) | (IP & 0x0f); duart68681->IP_last_state = IP; duart68681->ISR &= ~INT_INPUT_PORT_CHANGE; duart68681_update_interrupts(duart68681); } break; case 0x05: /* ISR */ r = duart68681->ISR; break; case 0x08: /* MR1B/MR2B */ if ( duart68681->channel[1].MR_ptr == 0 ) { r = duart68681->channel[1].MR1; duart68681->channel[1].MR_ptr = 1; } else { r = duart68681->channel[1].MR2; } break; case 0x09: /* SRB */ r = duart68681->channel[1].SR; break; case 0x0b: /* RHRB */ r = duart68681_read_rx_fifo(duart68681, 1); break; case 0x0d: /* IP */ if ( duart68681->duart_config->input_port_read != NULL ) r = duart68681->duart_config->input_port_read(duart68681->device); else { r = 0xff; /* if (input_code_pressed(KEYCODE_1)) r ^= 0x0001; if (input_code_pressed(KEYCODE_2)) r ^= 0x0002; if (input_code_pressed(KEYCODE_3)) r ^= 0x0004; if (input_code_pressed(KEYCODE_4)) r ^= 0x0008; if (input_code_pressed(KEYCODE_5)) r ^= 0x0010; if (input_code_pressed(KEYCODE_6)) r ^= 0x0020; if (input_code_pressed(KEYCODE_7)) r ^= 0x0040; if (input_code_pressed(KEYCODE_8)) r ^= 0x0080; */ } break; case 0x0e: /* Start counter command */ switch( (duart68681->ACR >> 4) & 0x07 ) { case 0x07: /* Timer, CLK/16 */ { double hz; //attotime rate = attotime_mul(ATTOTIME_IN_HZ(duart68681->clock), 16*duart68681->CTR.w.l); attotime rate = ATTOTIME_IN_HZ(2*device->clock/(2*16*16*duart68681->CTR.w.l)); hz = ATTOSECONDS_TO_HZ(rate.attoseconds); timer_adjust_periodic(duart68681->duart_timer, rate, 0, rate); } break; } break; case 0x0f: /* Stop counter command */ duart68681->ISR &= ~INT_COUNTER_READY; duart68681_update_interrupts(duart68681); break; default: LOG(( "Reading unhandled 68681 reg %x\n", offset )); break; } LOG(("returned %02x\n", r)); return r; } WRITE8_DEVICE_HANDLER(duart68681_w) { duart68681_state* duart68681 = get_safe_token(device); offset &= 0x0f; LOG(( "Writing 68681 (%s) reg %x (%s) with %04x\n", device->tag, offset, duart68681_reg_write_names[offset], data )); switch(offset) { case 0x00: /* MRA */ duart68681_write_MR(duart68681, 0, data); break; case 0x01: /* CSRA */ duart68681_write_CSR(duart68681, 0, data, duart68681->ACR); break; case 0x02: /* CRA */ duart68681_write_CR(duart68681, 0, data); break; case 0x03: /* THRA */ duart68681_write_TX(duart68681, 0, data); break; case 0x04: /* ACR */ duart68681->ACR = data; // bits 6-4: Counter/Timer Mode And Clock Source Select // bits 3-0: IP3-0 Change-Of-State Interrupt Enable if (((data >> 4) & 0x07) != 0x07) logerror( "68681 (%s): Unhandled timer/counter mode %d\n", device->tag, (data >> 4) & 0x07); duart68681_write_CSR(duart68681, 0, duart68681->channel[0].CSR, data); duart68681_write_CSR(duart68681, 1, duart68681->channel[1].CSR, data); break; case 0x05: /* IMR */ duart68681->IMR = data; duart68681_update_interrupts(duart68681); break; case 0x06: /* CTUR */ duart68681->CTR.b.h = data; break; case 0x07: /* CTLR */ duart68681->CTR.b.l = data; break; case 0x08: /* MRB */ duart68681_write_MR(duart68681, 1, data); break; case 0x09: /* CSRB */ duart68681_write_CSR(duart68681, 1, data, duart68681->ACR); break; case 0x0a: /* CRB */ duart68681_write_CR(duart68681, 1, data); break; case 0x0b: /* THRB */ duart68681_write_TX(duart68681, 1, data); break; case 0x0c: /* IVR */ duart68681->IVR = data; break; case 0x0d: /* OPCR */ if (data != 0x00) logerror( "68681 (%s): Unhandled OPCR value: %02x\n", device->tag, data); duart68681->OPCR = data; break; case 0x0e: /* Set Output Port Bits */ duart68681->OPR |= data; if (duart68681->duart_config->output_port_write) duart68681->duart_config->output_port_write(duart68681->device, duart68681->OPR ^ 0xff); break; case 0x0f: /* Reset Output Port Bits */ duart68681->OPR &= ~data; if (duart68681->duart_config->output_port_write) duart68681->duart_config->output_port_write(duart68681->device, duart68681->OPR ^ 0xff); break; } } void duart68681_rx_data( const device_config* device, int ch, UINT8 data ) { duart68681_state *duart68681 = get_safe_token(device); if ( duart68681->channel[ch].rx_enabled ) { if ( duart68681->channel[ch].rx_fifo_num >= RX_FIFO_SIZE ) { LOG(( "68681: FIFO overflow\n" )); return; } duart68681->channel[ch].rx_fifo[duart68681->channel[ch].rx_fifo_write_ptr++] = data; if ( duart68681->channel[ch].rx_fifo_write_ptr == RX_FIFO_SIZE ) { duart68681->channel[ch].rx_fifo_write_ptr = 0; } duart68681->channel[ch].rx_fifo_num++; duart68681->channel[ch].SR |= STATUS_RECEIVER_READY; if ( BIT(duart68681->channel[ch].MR1,MODE_RX_INT_SELECT_BIT) == 0 ) { duart68681->ISR |= (ch == 0) ? INT_RXRDY_FFULLA : INT_RXRDY_FFULLB; } if ( duart68681->channel[ch].rx_fifo_num == RX_FIFO_SIZE ) { duart68681->channel[ch].SR |= STATUS_FIFO_FULL; if ( BIT(duart68681->channel[ch].MR1,MODE_RX_INT_SELECT_BIT) == 1 ) { duart68681->ISR |= (ch == 0) ? INT_RXRDY_FFULLA : INT_RXRDY_FFULLB; } } duart68681_update_interrupts(duart68681); } }; /*------------------------------------------------- device start callback -------------------------------------------------*/ static DEVICE_START(duart68681) { duart68681_state *duart68681 = get_safe_token(device); /* validate arguments */ assert(device != NULL); assert(device->tag != NULL); state_save_register_device_item(device, 0, duart68681->ACR); state_save_register_device_item(device, 0, duart68681->IMR); state_save_register_device_item(device, 0, duart68681->ISR); state_save_register_device_item(device, 0, duart68681->IVR); state_save_register_device_item(device, 0, duart68681->OPCR); state_save_register_device_item(device, 0, duart68681->CTR); state_save_register_device_item(device, 0, duart68681->IP_last_state); state_save_register_device_item(device, 0, duart68681->channel[0].CR); state_save_register_device_item(device, 0, duart68681->channel[0].CSR); state_save_register_device_item(device, 0, duart68681->channel[0].MR1); state_save_register_device_item(device, 0, duart68681->channel[0].MR2); state_save_register_device_item(device, 0, duart68681->channel[0].MR_ptr); state_save_register_device_item(device, 0, duart68681->channel[0].SR); state_save_register_device_item(device, 0, duart68681->channel[0].baud_rate); state_save_register_device_item(device, 0, duart68681->channel[0].rx_enabled); state_save_register_device_item_array(device, 0, duart68681->channel[0].rx_fifo); state_save_register_device_item(device, 0, duart68681->channel[0].rx_fifo_read_ptr); state_save_register_device_item(device, 0, duart68681->channel[0].rx_fifo_write_ptr); state_save_register_device_item(device, 0, duart68681->channel[0].rx_fifo_num); state_save_register_device_item(device, 0, duart68681->channel[0].tx_enabled); state_save_register_device_item(device, 0, duart68681->channel[0].tx_data); state_save_register_device_item(device, 0, duart68681->channel[0].tx_ready); state_save_register_device_item(device, 0, duart68681->channel[1].CR); state_save_register_device_item(device, 0, duart68681->channel[1].CSR); state_save_register_device_item(device, 0, duart68681->channel[1].MR1); state_save_register_device_item(device, 0, duart68681->channel[1].MR2); state_save_register_device_item(device, 0, duart68681->channel[1].MR_ptr); state_save_register_device_item(device, 0, duart68681->channel[1].SR); state_save_register_device_item(device, 0, duart68681->channel[1].baud_rate); state_save_register_device_item(device, 0, duart68681->channel[1].rx_enabled); state_save_register_device_item_array(device, 0, duart68681->channel[1].rx_fifo); state_save_register_device_item(device, 0, duart68681->channel[1].rx_fifo_read_ptr); state_save_register_device_item(device, 0, duart68681->channel[1].rx_fifo_write_ptr); state_save_register_device_item(device, 0, duart68681->channel[1].rx_fifo_num); state_save_register_device_item(device, 0, duart68681->channel[1].tx_enabled); state_save_register_device_item(device, 0, duart68681->channel[1].tx_data); state_save_register_device_item(device, 0, duart68681->channel[1].tx_ready); return DEVICE_START_OK; } /*------------------------------------------------- device reset callback -------------------------------------------------*/ static DEVICE_RESET(duart68681) { duart68681_state *duart68681 = get_safe_token(device); memset(duart68681, 0, sizeof(duart68681_state)); duart68681->duart_config = device->static_config; duart68681->device = device; duart68681->IVR = 0x0f; // allocate timers duart68681->channel[0].tx_timer = timer_alloc(device->machine, tx_timer_callback, (void*)device); timer_adjust_oneshot(duart68681->channel[0].tx_timer, attotime_never, 0); duart68681->channel[1].tx_timer = timer_alloc(device->machine, tx_timer_callback, (void*)device); timer_adjust_oneshot(duart68681->channel[1].tx_timer, attotime_never, 1); duart68681->duart_timer = timer_alloc(device->machine, duart_timer_callback, (void*)device); } /*------------------------------------------------- device set info callback -------------------------------------------------*/ static DEVICE_SET_INFO(duart68681) { switch (state) { /* no parameters to set */ } } /*------------------------------------------------- device get info callback -------------------------------------------------*/ DEVICE_GET_INFO(duart68681) { switch (state) { /* --- the following bits of info are returned as 64-bit signed integers --- */ case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(duart68681_state); break; case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = sizeof(duart68681_config); break; case DEVINFO_INT_CLASS: info->i = DEVICE_CLASS_PERIPHERAL; break; /* --- the following bits of info are returned as pointers to data or functions --- */ case DEVINFO_FCT_SET_INFO: info->set_info = DEVICE_SET_INFO_NAME(duart68681); break; case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(duart68681); break; case DEVINFO_FCT_STOP: /* nothing */ break; case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(duart68681);break; /* --- the following bits of info are returned as NULL-terminated strings --- */ case DEVINFO_STR_NAME: info->s = "DUART 68681"; break; case DEVINFO_STR_FAMILY: info->s = "DUART"; break; case DEVINFO_STR_VERSION: info->s = "1.0"; break; case DEVINFO_STR_SOURCE_FILE: info->s = __FILE__; break; case DEVINFO_STR_CREDITS: info->s = "Copyright Nicola Salmoria and the MAME Team"; break; } }