changeset 1348:040c5600e2d9

Implemented slow rise time of IO pins set as inputs, but not driven by device. Fixes input in Decap Attack and possibly other games with buggy controller code
author Michael Pavone <pavone@retrodev.com>
date Mon, 08 May 2017 22:31:28 -0700
parents 4c4beb22b042
children 6c7bcb49e840
files genesis.c io.c io.h
diffstat 3 files changed, 73 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/genesis.c	Sat May 06 00:09:14 2017 -0700
+++ b/genesis.c	Mon May 08 22:31:28 2017 -0700
@@ -513,13 +513,13 @@
 				io_data_write(gen->io.ports+2, value, context->current_cycle);
 				break;
 			case 0x4:
-				gen->io.ports[0].control = value;
+				io_control_write(gen->io.ports, value, context->current_cycle);
 				break;
 			case 0x5:
-				gen->io.ports[1].control = value;
+				io_control_write(gen->io.ports+1, value, context->current_cycle);
 				break;
 			case 0x6:
-				gen->io.ports[2].control = value;
+				io_control_write(gen->io.ports+2, value, context->current_cycle);
 				break;
 			case 0x7:
 				gen->io.ports[0].serial_out = value;
--- a/io.c	Sat May 06 00:09:14 2017 -0700
+++ b/io.c	Mon May 08 22:31:28 2017 -0700
@@ -1506,6 +1506,16 @@
 			port->device.mouse.ready_cycle -= deduction;
 		}
 	}
+	for (int i = 0; i < 8; i++)
+	{
+		if (port->slow_rise_start[i] != CYCLE_NEVER) {
+			if (port->slow_rise_start[i] >= deduction) {
+				port->slow_rise_start[i] -= deduction;
+			} else {
+				port->slow_rise_start[i] = CYCLE_NEVER;
+			}
+		}
+	}
 	if (last_poll_cycle >= deduction) {
 		last_poll_cycle -= deduction;
 	} else {
@@ -1623,6 +1633,25 @@
 	KB_WRITE
 };
 
+void io_control_write(io_port *port, uint8_t value, uint32_t current_cycle)
+{
+	uint8_t changes = value ^ port->control;
+	if (changes) {
+		for (int i = 0; i < 8; i++)
+		{
+			if (!(value & 1 << i) && !(port->output & 1 << i)) {
+				//port switched from output to input and the output value was 0
+				//since there is a weak pull-up on input pins, this will lead
+				//to a slow rise from 0 to 1 if the pin isn't being externally driven
+				port->slow_rise_start[i] = current_cycle;
+			} else {
+				port->slow_rise_start[i] = CYCLE_NEVER;
+			}
+		}
+		port->control = value;
+	}
+}
+
 void io_data_write(io_port * port, uint8_t value, uint32_t current_cycle)
 {
 	uint8_t old_output = (port->control & port->output) | (~port->control & 0xFF);
@@ -1766,12 +1795,34 @@
 	return bytes;
 }
 
+#define SLOW_RISE_DEVICE (30*7)
+#define SLOW_RISE_INPUT (12*7)
+
+static uint8_t get_output_value(io_port *port, uint32_t current_cycle, uint32_t slow_rise_delay)
+{
+	uint8_t output = (port->control | 0x80) & port->output;
+	for (int i = 0; i < 8; i++)
+	{
+		if (!(port->control & 1 << i)) {
+			if (port->slow_rise_start[i] != CYCLE_NEVER) {
+				if (current_cycle - port->slow_rise_start[i] >= slow_rise_delay) {
+					output |= 1 << i;
+				}
+			} else {
+				output |= 1 << i;
+			}
+		}
+	}
+	return output;
+}
+
 uint8_t io_data_read(io_port * port, uint32_t current_cycle)
 {
+	uint8_t output = get_output_value(port, current_cycle, SLOW_RISE_DEVICE);
 	uint8_t control = port->control | 0x80;
-	uint8_t output = (control & port->output) | (~control & 0xFF);
 	uint8_t th = output & 0x40;
 	uint8_t input;
+	uint8_t device_driven;
 	if (current_cycle - last_poll_cycle > MIN_POLL_INTERVAL) {
 		process_events();
 		last_poll_cycle = current_cycle;
@@ -1789,6 +1840,7 @@
 		}
 		//controller output is logically inverted
 		input = ~input;
+		device_driven = 0x3F;
 		break;
 	}
 	case IO_GAMEPAD6:
@@ -1816,6 +1868,7 @@
 		}
 		//controller output is logically inverted
 		input = ~input;
+		device_driven = 0x3F;
 		break;
 	}
 	case IO_MOUSE:
@@ -1875,6 +1928,7 @@
 			}
 			input |= ((port->device.mouse.tr_counter & 1) == 0) << 4;
 		}
+		device_driven = 0x1F;
 		break;
 	}
 	case IO_SATURN_KEYBOARD:
@@ -1947,6 +2001,7 @@
 			}
 			input |= ((port->device.keyboard.tr_counter & 1) == 0) << 4;
 		}
+		device_driven = 0x1F;
 		break;
 	}
 	case IO_XBAND_KEYBOARD:
@@ -2008,6 +2063,8 @@
 				input = 0xF;
 			}
 			input |= ((port->device.keyboard.tr_counter & 1) == 0) << 4;
+			//this is not strictly correct at all times, but good enough for now
+			device_driven = 0x1F;
 		}
 		break;
 	}
@@ -2018,6 +2075,7 @@
 			service_pipe(port);
 		}
 		input = port->input[th ? IO_TH1 : IO_TH0];
+		device_driven = 0x3F;
 		break;
 	case IO_GENERIC:
 		if (port->input[IO_TH0] & 0x80 && port->input[IO_STATE] == IO_WRITTEN)
@@ -2027,13 +2085,20 @@
 		}
 		service_socket(port);
 		input = port->input[IO_TH0];
+		device_driven = 0x7F;
 		break;
 #endif
 	default:
-		input = 0xFF;
+		input = 0;
+		device_driven = 0;
 		break;
 	}
-	uint8_t value = (input & (~control)) | (port->output & control);
+	uint8_t value = (input & (~control) & device_driven) | (port->output & control);
+	//deal with pins that are configured as inputs, but not being actively driven by the device
+	uint8_t floating = (~device_driven) & (~control);
+	if (floating) {
+		value |= get_output_value(port, current_cycle, SLOW_RISE_INPUT) & floating;
+	}
 	/*if (port->input[GAMEPAD_TH0] || port->input[GAMEPAD_TH1]) {
 		printf ("value: %X\n", value);
 	}*/
--- a/io.h	Sat May 06 00:09:14 2017 -0700
+++ b/io.h	Mon May 08 22:31:28 2017 -0700
@@ -60,6 +60,7 @@
 	uint8_t  output;
 	uint8_t  control;
 	uint8_t  input[3];
+	uint32_t slow_rise_start[8];
 	uint8_t  serial_out;
 	uint8_t  serial_in;
 	uint8_t  serial_ctrl;
@@ -95,6 +96,7 @@
 void map_all_bindings(sega_io *io);
 void setup_io_devices(tern_node * config, rom_info *rom, sega_io *io);
 void io_adjust_cycles(io_port * pad, uint32_t current_cycle, uint32_t deduction);
+void io_control_write(io_port *port, uint8_t value, uint32_t current_cycle);
 void io_data_write(io_port * pad, uint8_t value, uint32_t current_cycle);
 uint8_t io_data_read(io_port * pad, uint32_t current_cycle);
 void handle_keydown(int keycode, uint8_t scancode);