changeset 2065:02a9846668d1 segacd

Implement transfer of data from CDC to elsewhere. Other miscellaneous CDD/CDC improvements
author Michael Pavone <pavone@retrodev.com>
date Sat, 29 Jan 2022 17:43:37 -0800
parents 91e4d2fe5cd9
children a61a8a87410c
files cdd_mcu.c cdd_mcu.h lc8951.c lc8951.h segacd.c segacd.h
diffstat 6 files changed, 224 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- a/cdd_mcu.c	Fri Jan 28 22:48:06 2022 -0800
+++ b/cdd_mcu.c	Sat Jan 29 17:43:37 2022 -0800
@@ -153,7 +153,7 @@
 		switch (context->requested_format)
 		{
 		case SF_ABSOLUTE:
-			if (context->toc_valid) {
+			if (context->toc_valid && context->head_pba >= LEADIN_SECTORS) {
 				lba_to_status(context, context->head_pba - LEADIN_SECTORS);
 				context->status_buffer.format = SF_ABSOLUTE;
 			} else {
@@ -161,7 +161,7 @@
 			}
 			break;
 		case SF_RELATIVE:
-			if (context->toc_valid) {
+			if (context->toc_valid && context->head_pba >= LEADIN_SECTORS) {
 				uint32_t lba =context->head_pba - LEADIN_SECTORS;
 				for (uint32_t i = 0; i < context->media->num_tracks; i++)
 				{
@@ -187,7 +187,49 @@
 					}
 				}
 				lba_to_status(context, lba);
-				context->status_buffer.format = SF_ABSOLUTE;
+				context->status_buffer.format = SF_RELATIVE;
+			} else {
+				context->status_buffer.format = SF_NOTREADY;
+			}
+			break;
+		case SF_TRACK:
+			if (context->toc_valid && context->head_pba >= LEADIN_SECTORS) {
+				uint32_t lba =context->head_pba - LEADIN_SECTORS;
+				uint32_t i;
+				for (i = 0; i < context->media->num_tracks; i++)
+				{
+					if (lba < context->media->tracks[i].end_lba) {
+						if (context->media->tracks[i].fake_pregap) {
+							if (lba > context->media->tracks[i].fake_pregap) {
+								lba -= context->media->tracks[i].fake_pregap;
+							} else {
+								//relative time counts down to 0 in pregap
+								lba = context->media->tracks[i].fake_pregap - lba;
+								break;
+							}
+						}
+						if (lba < context->media->tracks[i].start_lba) {
+							//relative time counts down to 0 in pregap
+							lba = context->media->tracks[i].start_lba - lba;
+						} else {
+							lba -= context->media->tracks[i].start_lba;
+						}
+						break;
+					} else if (context->media->tracks[i].fake_pregap) {
+						lba -= context->media->tracks[i].fake_pregap;
+					}
+				}
+				context->status_buffer.b.track.track_high = (i + 1) / 10;
+				context->status_buffer.b.track.track_low = (i + 1) % 10;
+				if (context->media->tracks[i].type == TRACK_DATA) {
+					context->status_buffer.b.track.control = 4;
+				} else {
+					//TODO: pre-emphasis flag
+					//TODO: copy permitted flag
+					context->status_buffer.b.track.control = 0;
+				}
+				context->status_buffer.b.track.adr = 1;
+				context->status_buffer.format = SF_TRACK;
 			} else {
 				context->status_buffer.format = SF_NOTREADY;
 			}
@@ -220,10 +262,13 @@
 		case SF_TOCN:
 			if (context->toc_valid) {
 				uint32_t lba = context->media->tracks[context->requested_track - 1].start_lba;
-				if (context->requested_track > 1) {
-					lba += context->media->tracks[1].fake_pregap;
+				for (uint32_t i = 0; i < context->requested_track; i++) {
+					lba += context->media->tracks[i].fake_pregap;
 				}
 				lba_to_status(context, lba);
+				if (context->media->tracks[context->requested_track - 1].type == TRACK_DATA) {
+					context->status_buffer.b.tocn.frame_low |= 0x80;
+				}
 				context->status_buffer.b.tocn.track_low = context->requested_track % 10;
 				context->status_buffer.format = SF_TOCN;
 			} else {
@@ -238,6 +283,8 @@
 		if (context->error_status == DS_STOP) {
 			if (context->requested_format >= SF_TOCO && context->requested_format <= SF_TOCN) {
 				context->status_buffer.status = DS_TOC_READ;
+			} else if (context->seeking) {
+				context->status_buffer.status = DS_SEEK;
 			} else {
 				context->status_buffer.status = context->status;
 			}
@@ -305,7 +352,11 @@
 		lba += context->cmd_buffer.b.time.sec_high * 10 + context->cmd_buffer.b.time.sec_low;
 		lba *= 75;
 		lba += context->cmd_buffer.b.time.frame_high * 10 + context->cmd_buffer.b.time.frame_low;
-		printf("READ/SEEK cmd for lba %d\n", lba);
+		printf("READ/SEEK cmd for lba %d, MM:SS:FF %u%u:%u%u:%u%u\n", lba,
+			context->cmd_buffer.b.time.min_high, context->cmd_buffer.b.time.min_low,
+			context->cmd_buffer.b.time.sec_high, context->cmd_buffer.b.time.sec_low,
+			context->cmd_buffer.b.time.frame_high, context->cmd_buffer.b.time.frame_low
+		);
 		if (lba >= context->media->tracks[0].fake_pregap + context->media->tracks[context->media->num_tracks - 1].end_lba) {
 			context->error_status = DS_CMD_ERROR;
 			break;
--- a/cdd_mcu.h	Fri Jan 28 22:48:06 2022 -0800
+++ b/cdd_mcu.h	Sat Jan 29 17:43:37 2022 -0800
@@ -69,7 +69,7 @@
 			uint8_t padding0;
 			uint8_t padding1;
 			uint8_t control;
-			uint8_t padding2;
+			uint8_t adr;
 			uint8_t flags;
 		} track;
 		struct {
--- a/lc8951.c	Fri Jan 28 22:48:06 2022 -0800
+++ b/lc8951.c	Sat Jan 29 17:43:37 2022 -0800
@@ -64,13 +64,17 @@
 //3 cycles for memory operation
 //6 cycles min for DMA-mode host transfer
 
-void lc8951_init(lc8951 *context)
+void lc8951_init(lc8951 *context, lcd8951_byte_recv_fun byte_handler, void *handler_data)
 {
 	//This seems to vary somewhat between Sega CD models
 	//unclear if the difference is in the lc8951 or gate array
 	context->regs[IFSTAT] = 0xFF;
 	context->ar_mask = 0x1F;
 	context->clock_step = (2 + 2) * 6; // external divider, internal divider + DMA period
+	context->byte_handler = byte_handler;
+	context->handler_data = handler_data;
+	context->decode_end = CYCLE_NEVER;
+	context->transfer_end = CYCLE_NEVER;
 }
 
 void lc8951_reg_write(lc8951 *context, uint8_t value)
@@ -91,6 +95,7 @@
 		}
 		if (!(value & BIT_DOUTEN)) {
 			context->regs[IFSTAT] |= BIT_DTBSY;
+			context->transfer_end = CYCLE_NEVER;
 		}
 		break;
 	case DBCL:
@@ -108,10 +113,11 @@
 		context->dac |= value << 8;
 		break;
 	case DTTRG:
-		if (value & BIT_DOUTEN) {
+		if (context->ifctrl & BIT_DOUTEN) {
 			context->regs[IFSTAT] &= ~BIT_DTBSY;
 			uint16_t transfer_size = context->regs[DBCL] | (context->regs[DBCH] << 8);
 			context->transfer_end = context->cycle + transfer_size * context->clock_step;
+			printf("DTTRG: size %u, cycle %u, end %u\n", transfer_size, context->cycle, context->transfer_end);
 		}
 		break;
 	case DTACK:
@@ -208,23 +214,39 @@
 			printf("Decode done %X:%X:%X mode %X\n", context->regs[HEAD0], context->regs[HEAD1], context->regs[HEAD2], context->regs[HEAD3]);
 		}
 		if (context->transfer_end != CYCLE_NEVER) {
-			//TODO: transfer byte to gate array destination
-			context->dac++;
-			context->regs[DBCL]--;
-			if (!context->regs[DBCL]) {
-				context->regs[DBCH]--;
-				if (!context->regs[DBCH]) {
-					context->regs[IFSTAT] &= ~BIT_DTEI;
-					if (context->cycle != context->transfer_end) {
-						printf("Expected transfer end at %u but ended at %u\n", context->transfer_end, context->cycle);
+			if (context->byte_handler(context->handler_data, context->buffer[context->dac & (sizeof(context->buffer)-1)])) {
+				context->dac++;
+				context->regs[DBCL]--;
+				if (context->regs[DBCL] == 0xFF) {
+					context->regs[DBCH]--;
+					if (context->regs[DBCH] == 0xFF) {
+						context->regs[IFSTAT] &= ~BIT_DTEI;
+						context->regs[IFSTAT] |= BIT_DTBSY;
+						if (context->cycle != context->transfer_end) {
+							printf("Expected transfer end at %u but ended at %u\n", context->transfer_end, context->cycle);
+						}
+						context->transfer_end = CYCLE_NEVER;
 					}
-					context->transfer_end = CYCLE_NEVER;
 				}
+			} else {
+				// pause transfer
+				context->transfer_end = CYCLE_NEVER;
 			}
 		}
 	}
 }
 
+void lc8951_resume_transfer(lc8951 *context)
+{
+	if (context->transfer_end == CYCLE_NEVER && (context->ifctrl & BIT_DOUTEN)) {
+		uint16_t transfer_size = context->regs[DBCL] | (context->regs[DBCH] << 8);
+		if (transfer_size) {
+			context->transfer_end = context->cycle + transfer_size * context->clock_step;
+			printf("RESUME: size %u, cycle %u, end %u\n", transfer_size, context->cycle, context->transfer_end);
+		}
+	}
+}
+
 void lc8951_write_byte(lc8951 *context, uint32_t cycle, int sector_offset, uint8_t byte)
 {
 	lc8951_run(context, cycle);
@@ -251,7 +273,6 @@
 		}
 	}
 	if ((context->ctrl0 & (BIT_DECEN|BIT_WRRQ)) == (BIT_DECEN|BIT_WRRQ)) {
-		printf("lc8951_write_byte: %X - [%X] = %X\n", sector_offset, current_write_addr, byte);
 		context->buffer[current_write_addr & (sizeof(context->buffer)-1)] = byte;
 		context->regs[WAL]++;
 		if (!context->regs[WAL]) {
@@ -262,7 +283,7 @@
 
 uint32_t lc8951_next_interrupt(lc8951 *context)
 {
-	if ((!context->regs[IFSTAT]) & context->ifctrl & (BIT_CMDI|BIT_DTEI|BIT_DECI)) {
+	if ((~context->regs[IFSTAT]) & context->ifctrl & (BIT_CMDI|BIT_DTEI|BIT_DECI)) {
 		//interrupt already pending
 		return context->cycle;
 	}
--- a/lc8951.h	Fri Jan 28 22:48:06 2022 -0800
+++ b/lc8951.h	Sat Jan 29 17:43:37 2022 -0800
@@ -3,35 +3,40 @@
 
 #include <stdint.h>
 
+typedef uint8_t (*lcd8951_byte_recv_fun)(void *data, uint8_t byte);
+
 typedef struct {
+	lcd8951_byte_recv_fun byte_handler;
+	void     *handler_data;
 	uint32_t cycle;
 	uint32_t clock_step;
 	uint32_t decode_end;
 	uint32_t transfer_end;
 
-	uint8_t buffer[0x4000];
+	uint8_t  buffer[0x4000];
 
-	uint8_t regs[16];
-	uint8_t comin[8];
+	uint8_t  regs[16];
+	uint8_t  comin[8];
 
 	uint16_t dac;
-	uint8_t comin_write;
-	uint8_t comin_count;
-	uint8_t ifctrl;
-	uint8_t ctrl0;
-	uint8_t ctrl1;
-	uint8_t ar;
-	uint8_t ar_mask;
-	uint8_t decoding;
+	uint8_t  comin_write;
+	uint8_t  comin_count;
+	uint8_t  ifctrl;
+	uint8_t  ctrl0;
+	uint8_t  ctrl1;
+	uint8_t  ar;
+	uint8_t  ar_mask;
+	uint8_t  decoding;
 	uint16_t ptl_internal;
 } lc8951;
 
-void lc8951_init(lc8951 *context);
+void lc8951_init(lc8951 *context, lcd8951_byte_recv_fun byte_handler, void *handler_data);
 void lc8951_run(lc8951 *context, uint32_t cycle);
 void lc8951_reg_write(lc8951 *context, uint8_t value);
 uint8_t lc8951_reg_read(lc8951 *context);
 void lc8951_ar_write(lc8951 *context, uint8_t value);
 void lc8951_write_byte(lc8951 *context, uint32_t cycle, int sector_offset, uint8_t byte);
 uint32_t lc8951_next_interrupt(lc8951 *context);
+void lc8951_resume_transfer(lc8951 *context);
 
 #endif //LC8951_H_
--- a/segacd.c	Fri Jan 28 22:48:06 2022 -0800
+++ b/segacd.c	Sat Jan 29 17:43:37 2022 -0800
@@ -73,6 +73,18 @@
 #define BIT_DMNA       0x0002
 #define BIT_RET        0x0001
 
+//GA_CDC_CTRL
+#define BIT_EDT        0x8000
+#define BIT_DSR        0x4000
+
+enum {
+	DST_MAIN_CPU = 2,
+	DST_SUB_CPU,
+	DST_PCM_RAM,
+	DST_PROG_RAM,
+	DST_WORD_RAM = 7
+};
+
 //GA_INT_MASK
 #define BIT_MASK_IEN1  0x0002
 #define BIT_MASK_IEN2  0x0004
@@ -383,10 +395,25 @@
 	case GA_MEM_MODE:
 		return cd->gate_array[reg] & 0xFF1F;
 	case GA_CDC_CTRL:
+		cdd_run(cd, m68k->current_cycle);
 		return cd->gate_array[reg] | cd->cdc.ar;
 	case GA_CDC_REG_DATA:
 		cdd_run(cd, m68k->current_cycle);
 		return lc8951_reg_read(&cd->cdc);
+	case GA_CDC_HOST_DATA: {
+		uint16_t dst = cd->gate_array[GA_CDC_CTRL] >> 8 & 0x7;
+		if (dst == DST_SUB_CPU) {
+			cdd_run(cd, m68k->current_cycle);
+			if (cd->gate_array[GA_CDC_CTRL] & BIT_DSR) {
+				cd->gate_array[GA_CDC_CTRL] &= ~BIT_DSR;
+				lc8951_resume_transfer(&cd->cdc);
+			}
+			calculate_target_cycle(cd->m68k);
+			return cd->gate_array[reg];
+		} else {
+			return 0xFFFF;
+		}
+	}
 	case GA_STOP_WATCH:
 	case GA_TIMER:
 		timers_run(cd, m68k->current_cycle);
@@ -508,6 +535,11 @@
 		lc8951_reg_write(&cd->cdc, value);
 		calculate_target_cycle(m68k);
 		break;
+	case GA_CDC_DMA_ADDR:
+		cdd_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value;
+		cd->cdc_dst_low = 0;
+		break;
 	case GA_STOP_WATCH:
 		//docs say you should only write zero to reset
 		//mcd-verificator comments suggest any value will reset
@@ -621,6 +653,73 @@
 	return sub_gate_write16(address, vcontext, value16);
 }
 
+static uint8_t handle_cdc_byte(void *vsys, uint8_t value)
+{
+	segacd_context *cd = vsys;
+	uint16_t dest = cd->gate_array[GA_CDC_CTRL] >> 8 & 0x7;
+	uint32_t dma_addr = cd->gate_array[GA_CDC_DMA_ADDR] << 3;
+	dma_addr |= cd->cdc_dst_low;
+	switch (dest)
+	{
+	case DST_MAIN_CPU:
+	case DST_SUB_CPU:
+		if (cd->gate_array[GA_CDC_CTRL] & BIT_DSR) {
+			//host reg is already full, pause transfer
+			return 0;
+		}
+		if (cd->cdc_dst_low) {
+			cd->gate_array[GA_CDC_HOST_DATA] &= 0xFF00;
+			cd->gate_array[GA_CDC_HOST_DATA] |= value;
+			cd->cdc_dst_low = 0;
+			cd->gate_array[GA_CDC_CTRL] |= BIT_DSR;
+		} else {
+			cd->gate_array[GA_CDC_HOST_DATA] &= 0xFF;
+			cd->gate_array[GA_CDC_HOST_DATA] |= value << 8;
+			cd->cdc_dst_low = 1;
+			return 1;
+		}
+		break;
+	case DST_PCM_RAM:
+		dma_addr &= (1 << 13) - 1;
+		//TODO: write to currently visible 8K bank of PCM RAM I guess?
+		dma_addr++;
+		cd->cdc_dst_low = dma_addr & 7;
+		cd->gate_array[GA_CDC_DMA_ADDR] = dma_addr >> 3;
+		break;
+	case DST_PROG_RAM:
+		((uint8_t*)cd->prog_ram)[dma_addr ^ 1] = value;
+		m68k_invalidate_code_range(cd->m68k, dma_addr, dma_addr + 1);
+		dma_addr++;
+		cd->cdc_dst_low = dma_addr & 7;
+		cd->gate_array[GA_CDC_DMA_ADDR] = dma_addr >> 3;
+		break;
+	case DST_WORD_RAM:
+		if (cd->gate_array[GA_MEM_MODE] & BIT_MEM_MODE) {
+			//1M mode, write to bank assigned to Sub CPU
+			dma_addr &= (1 << 17) - 1;
+			((uint8_t*)cd->m68k->mem_pointers[1])[dma_addr ^ 1] = value;
+			m68k_invalidate_code_range(cd->m68k, 0x0C0000 + dma_addr, 0x0C0000 + dma_addr + 1);
+		} else {
+			//2M mode, check if Sub CPU has access
+			if (!(cd->gate_array[GA_MEM_MODE] & BIT_RET)) {
+				dma_addr &= (1 << 18) - 1;
+				((uint8_t*)cd->word_ram)[dma_addr ^ 1] = value;
+				m68k_invalidate_code_range(cd->m68k, 0x080000 + dma_addr, 0x080000 + dma_addr + 1);
+			}
+		}
+		dma_addr++;
+		cd->cdc_dst_low = dma_addr & 7;
+		cd->gate_array[GA_CDC_DMA_ADDR] = dma_addr >> 3;
+		break;
+	default:
+		printf("Invalid CDC transfer destination %d\n", dest);
+	}
+	if (cd->cdc.cycle == cd->cdc.transfer_end) {
+		cd->gate_array[GA_CDC_CTRL] |= BIT_EDT;
+	}
+	return 1;
+}
+
 static uint8_t can_main_access_prog(segacd_context *cd)
 {
 	//TODO: use actual busack
@@ -725,6 +824,19 @@
 		return cd->gate_array[offset] & 0xFFE7;
 	case GA_HINT_VECTOR:
 		return cd->rom_mut[0x72/2];
+	case GA_CDC_HOST_DATA: {
+		uint16_t dst = cd->gate_array[GA_CDC_CTRL] >> 8 & 0x7;
+		if (dst == DST_MAIN_CPU) {
+			if (cd->gate_array[GA_CDC_CTRL] & BIT_DSR) {
+				cd->gate_array[GA_CDC_CTRL] &= ~BIT_DSR;
+				lc8951_resume_transfer(&cd->cdc);
+			}
+			calculate_target_cycle(cd->m68k);
+			return cd->gate_array[offset];
+		} else {
+			return 0xFFFF;
+		}
+	}
 	case GA_CDC_DMA_ADDR:
 		//TODO: open bus maybe?
 		return 0xFFFF;
@@ -927,7 +1039,7 @@
 	cd->memptr_start_index = 0;
 	cd->gate_array[1] = 1;
 	cd->gate_array[0x1B] = 0x100;
-	lc8951_init(&cd->cdc);
+	lc8951_init(&cd->cdc, handle_cdc_byte, cd);
 	if (media->chain && media->type != MEDIA_CDROM) {
 		media = media->chain;
 	}
--- a/segacd.h	Fri Jan 28 22:48:06 2022 -0800
+++ b/segacd.h	Sat Jan 29 17:43:37 2022 -0800
@@ -29,6 +29,7 @@
 	uint8_t         memptr_start_index;
 	lc8951          cdc;
 	cdd_mcu         cdd;
+	uint8_t         cdc_dst_low;
 } segacd_context;
 
 segacd_context *alloc_configure_segacd(system_media *media, uint32_t opts, uint8_t force_region, rom_info *info);