Mercurial > repos > blastem
view lc8951.c @ 2321:2eda5f81f91e
More fully baked ROM db support for SMS
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Thu, 15 Jun 2023 09:36:11 -0700 |
parents | 9ead0fe69d9b |
children | 9f0c67e5c50a |
line wrap: on
line source
#include "lc8951.h" #include "backend.h" enum { COMIN, IFSTAT, DBCL, DBCH, HEAD0, HEAD1, HEAD2, HEAD3, PTL, PTH, WAL, WAH, STAT0, STAT1, STAT2, STAT3, SBOUT = COMIN, IFCTRL = IFSTAT, DACL = HEAD0, DACH = HEAD1, DTTRG = HEAD2, DTACK = HEAD3, WAL_WRITE = PTL, WAH_WRITE = PTH, CTRL0 = WAL, CTRL1 = WAH, PTL_WRITE = STAT0, PTH_WRITE = STAT1, RESET = STAT3 }; //IFCTRL #define BIT_CMDIEN 0x80 #define BIT_DTEIEN 0x40 #define BIT_DECIEN 0x20 #define BIT_CMDBK 0x10 #define BIT_DTWAI 0x08 #define BIT_STWAI 0x04 #define BIT_DOUTEN 0x02 #define BIT_SOUTEN 0x01 //IFSTAT #define BIT_CMDI 0x80 #define BIT_DTEI 0x40 #define BIT_DECI 0x20 #define BIT_DTBSY 0x08 #define BIT_STBSY 0x04 #define BIT_DTEN 0x02 #define BIT_STEN 0x01 //CTRL0 #define BIT_DECEN 0x80 #define BIT_WRRQ 0x04 #define BIT_ORQ 0x02 #define BIT_PRQ 0x01 //CTRL1 #define BIT_SYIEN 0x80 #define BIT_SYDEN 0x40 //STAT0 #define BIT_CRCOK 0x80 #define BIT_ILSYNC 0x40 #define BIT_NOSYNC 0x20 #define BIT_LBLK 0x10 #define BIT_SBLK 0x04 #define BIT_UCEBLK 0x01 //STAT3 #define BIT_VALST 0x80 #define BIT_WLONG 0x40 //datasheet timing info //3 cycles for memory operation //6 cycles min for DMA-mode host transfer 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); // external divider, internal divider context->cycles_per_byte = context->clock_step * 6; context->byte_handler = byte_handler; context->handler_data = handler_data; context->decode_end = CYCLE_NEVER; context->transfer_end = CYCLE_NEVER; context->next_byte_cycle = CYCLE_NEVER; } void lc8951_set_dma_multiple(lc8951 *context, uint32_t multiple) { context->cycles_per_byte = context->clock_step * multiple; if (context->transfer_end != CYCLE_NEVER) { uint16_t transfer_size = context->regs[DBCL] | (context->regs[DBCH] << 8); context->transfer_end = context->next_byte_cycle + transfer_size * context->cycles_per_byte; } } void lc8951_reg_write(lc8951 *context, uint8_t value) { switch (context->ar) { case SBOUT: context->regs[context->ar] = value; if (context->ifctrl & BIT_SOUTEN) { context->regs[IFSTAT] &= ~BIT_STBSY; } break; case IFCTRL: context->ifctrl = value; if (!(value & BIT_SOUTEN)) { context->regs[IFSTAT] |= BIT_STBSY; } if (!(value & BIT_DOUTEN)) { context->regs[IFSTAT] |= BIT_DTBSY|BIT_DTEI; context->transfer_end = CYCLE_NEVER; } break; case DBCL: context->regs[context->ar] = value; break; case DBCH: context->regs[context->ar] = value & 0xF; break; case DACL: context->dac &= 0xFF00; context->dac |= value; break; case DACH: context->dac &= 0xFF; context->dac |= value << 8; break; case DTTRG: 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->cycles_per_byte; context->next_byte_cycle = context->cycle; context->triggered = 1; printf("DTTRG: size %u, cycle %u, end %u\n", transfer_size, context->cycle, context->transfer_end); } break; case DTACK: context->regs[IFSTAT] |= BIT_DTEI; break; case WAL_WRITE: context->regs[WAL] = value; break; case WAH_WRITE: context->regs[WAH] = value; break; case CTRL0: context->ctrl0 = value; break; case CTRL1: context->ctrl1 = value; break; case PTL_WRITE: context->regs[PTL] = value; break; case PTH_WRITE: context->regs[PTH] = value; //TODO: Datasheet says any write to PT triggers a decode, but initial tests suggest that's not the case //Need to do more tests with other CTRL0/CTRL1 settings //context->decode_end = context->cycle + 2352 * context->clock_step * 4; break; case RESET: context->comin_count = 0; context->regs[IFSTAT] = 0xFF; context->ifctrl = 0; context->ctrl0 = 0; context->ctrl1 = 0; break; default: break; } if (context->ar != SBOUT) { context->ar++; context->ar &= context->ar_mask; } } uint8_t lc8951_reg_read(lc8951 *context) { uint8_t value; if (context->ar == COMIN) { if (!context->comin_count) { return 0xFF; } value = context->comin[(context->comin_write - context->comin_count)&sizeof(context->comin)]; context->comin_count--; if (!context->comin_count) { context->regs[IFSTAT] |= BIT_CMDI; } return value; } if (context->ar == STAT3) { context->regs[IFSTAT] |= BIT_DECI; } if (context->ar >= sizeof(context->regs)) { value = 0xFF; } else { value = context->regs[context->ar]; } printf("CDC read %X: %X\n", context->ar, value); context->ar++; context->ar &= context->ar_mask; return value; } void lc8951_ar_write(lc8951 *context, uint8_t value) { context->ar = value & context->ar_mask; } //25 MHz clock input (1/2 SCD MCLK) //internal /2 divider //3 cycles for each SRAM access (though might be crystal frequency rather than internal frequency) //6 cycle period for DMA transfer out // void lc8951_run(lc8951 *context, uint32_t cycle) { for(; context->cycle < cycle; context->cycle += context->clock_step) { if (context->cycle >= context->decode_end) { context->decode_end = CYCLE_NEVER; context->regs[IFSTAT] &= ~BIT_DECI; context->regs[STAT3] &= ~BIT_VALST; if (context->ctrl0 & BIT_WRRQ) { uint16_t block_start = (context->regs[PTL] | (context->regs[PTH] << 8)) & (sizeof(context->buffer)-1); for (int reg = HEAD0; reg < PTL; reg++) { context->regs[reg] =context->buffer[block_start++]; block_start &= (sizeof(context->buffer)-1); } } printf("Decode done %X:%X:%X mode %X\n", context->regs[HEAD0], context->regs[HEAD1], context->regs[HEAD2], context->regs[HEAD3]); // This check is a hack until I properly implement error detection if (context->regs[HEAD0] < 0x74 && (context->regs[HEAD0] & 0xF) < 0xA && context->regs[HEAD1] < 0x60 && (context->regs[HEAD1] & 0xF) < 0xA && context->regs[HEAD2] < 0x75 && (context->regs[HEAD2] & 0xF) < 0xA && context->regs[HEAD3] < 3 && !(context->regs[STAT0] & (BIT_NOSYNC|BIT_ILSYNC)) ) { if (context->ctrl0 & (BIT_WRRQ|BIT_ORQ|BIT_PRQ)) { context->regs[STAT0] |= BIT_CRCOK; } context->regs[STAT1] = 0; context->regs[STAT2] = 0x10; } else { if (context->ctrl0 & (BIT_WRRQ|BIT_ORQ|BIT_PRQ)) { context->regs[STAT0] |= BIT_UCEBLK; } context->regs[STAT1] = 0xFF; context->regs[STAT2] = 0xF2; } context->regs[STAT3] |= BIT_WLONG; } if (context->cycle >= context->next_byte_cycle) { if (context->byte_handler(context->handler_data, context->buffer[context->dac & (sizeof(context->buffer)-1)])) { context->next_byte_cycle += context->cycles_per_byte; 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->next_byte_cycle = CYCLE_NEVER; context->triggered = 0; } } } else { // pause transfer context->next_byte_cycle = CYCLE_NEVER; context->transfer_end = CYCLE_NEVER; } } } } void lc8951_resume_transfer(lc8951 *context, uint32_t cycle) { if (context->triggered && context->transfer_end == CYCLE_NEVER && (context->ifctrl & BIT_DOUTEN)) { uint16_t transfer_size = context->regs[DBCL] | (context->regs[DBCH] << 8); //HACK!!! Work around Sub CPU running longer than we would like and dragging other components with it uint32_t step_diff = (context->cycle - cycle) / context->clock_step; if (step_diff) { context->cycle -= step_diff * context->clock_step; } context->transfer_end = context->cycle + transfer_size * context->cycles_per_byte; context->next_byte_cycle = context->cycle; if (step_diff) { lc8951_run(context, cycle); } } } void lc8951_write_byte(lc8951 *context, uint32_t cycle, int sector_offset, uint8_t byte) { lc8951_run(context, cycle); uint16_t current_write_addr = context->regs[WAL] | (context->regs[WAH] << 8); uint8_t sync_detected = 0, sync_ignored = 0; if (byte == 0) { // HACK!: The (sector_offset < 0x10) check is not correct, but without it Thunderhawk gets stuck // It has a sector that contains the sync pattern in the main data area // From the LC8951 datasheet, I would expect tohis to trigger a short block, but either // it's sync detection is fancier than I thought or I have a bug that is confusing the BIOS if (context->sync_counter == 11 && ((sector_offset & 3) == 3) && (sector_offset < 0x10)) { if (context->ctrl1 & BIT_SYDEN) { sync_detected = 1; } else { sync_ignored = 1; } context->sync_counter = 0; } else { context->sync_counter = 1; } } else if (byte == 0xFF && context->sync_counter) { context->sync_counter++; } else { context->sync_counter = 0; } uint8_t sync_inserted = 0; if (context->ctrl1 & BIT_SYIEN && context->sector_counter == 2351) { sync_inserted = 1; } if (context->sector_counter < 4) { //TODO: Handle SHDREN = 1 if ((context->ctrl0 & (BIT_DECEN|BIT_WRRQ)) == (BIT_DECEN)) { //monitor only mode context->regs[HEAD0 + context->sector_counter] = byte; } } if (sync_detected || sync_inserted) { //we've recevied the sync pattern for the next block context->regs[STAT0] &= ~(BIT_ILSYNC | BIT_NOSYNC | BIT_LBLK | BIT_SBLK); if (sync_inserted && !(sync_detected || sync_ignored)) { context->regs[STAT0] |= BIT_NOSYNC; } if (sync_detected && context->sector_counter != 2351) { context->regs[STAT0] |= BIT_ILSYNC; } context->sector_counter = 0; //header/status regs no longer considered "valid" context->regs[STAT3] |= BIT_VALST; //!DECI is set inactive at the same time as !VALST context->regs[IFSTAT] |= BIT_DECI; //clear error detection status bits context->regs[STAT0] &= ~(BIT_CRCOK|BIT_UCEBLK); context->regs[STAT3] &= ~BIT_WLONG; if (context->ctrl0 & BIT_DECEN) { if (context->ctrl0 & BIT_WRRQ) { uint16_t block_start = current_write_addr + 1 - 2352; context->regs[PTL] = block_start; context->regs[PTH] = block_start >> 8; } printf("Decoding block starting at %X (WRRQ: %d)\n", context->regs[PTL] | (context->regs[PTH] << 8), !!(context->ctrl0 & BIT_WRRQ)); //Based on measurements of a Wondermega M1 (LC8951) with SYDEN, SYIEN and DECEN only context->decode_end = context->cycle + 22030 * context->clock_step; } } else { context->sector_counter++; context->sector_counter &= 0xFFF; if (sync_ignored) { context->regs[STAT0] |= BIT_SBLK; } if (context->sector_counter == 2352) { context->regs[STAT0] |= BIT_LBLK; } } if ((context->ctrl0 & (BIT_DECEN|BIT_WRRQ)) == (BIT_DECEN|BIT_WRRQ)) { context->buffer[current_write_addr & (sizeof(context->buffer)-1)] = byte; context->regs[WAL]++; if (!context->regs[WAL]) { context->regs[WAH]++; } } } uint32_t lc8951_next_interrupt(lc8951 *context) { if ((~context->regs[IFSTAT]) & context->ifctrl & (BIT_CMDI|BIT_DTEI|BIT_DECI)) { //interrupt already pending return context->cycle; } uint32_t deci_cycle = CYCLE_NEVER; if (context->ifctrl & BIT_DECIEN) { deci_cycle = context->decode_end; } uint32_t dtei_cycle = CYCLE_NEVER; if (context->ifctrl & BIT_DTEIEN) { dtei_cycle = context->transfer_end; } return deci_cycle < dtei_cycle ? deci_cycle : dtei_cycle; } void lc8951_adjust_cycles(lc8951 *context, uint32_t deduction) { printf("CDC deduction of %u cycles @ %u, ", deduction, context->cycle); context->cycle -= deduction; if (context->decode_end != CYCLE_NEVER) { context->decode_end -= deduction; } if (context->transfer_end != CYCLE_NEVER) { context->transfer_end -= deduction; } if (context->next_byte_cycle != CYCLE_NEVER) { context->next_byte_cycle -= deduction; } printf("cycle is now %u, decode_end %u, transfer_end %u\n", context->cycle, context->decode_end, context->transfer_end); } void lc8951_serialize(lc8951 *context, serialize_buffer *buf) { save_int32(buf, context->cycle); save_int32(buf, context->decode_end); save_int32(buf, context->transfer_end); save_int32(buf, context->next_byte_cycle); save_int16(buf, context->sector_counter); save_buffer8(buf, context->buffer, sizeof(context->buffer)); save_buffer8(buf, context->regs, sizeof(context->regs)); save_buffer8(buf, context->comin, sizeof(context->comin)); save_int16(buf, context->dac); save_int8(buf, context->comin_write); save_int8(buf, context->comin_count); save_int8(buf, context->ifctrl); save_int8(buf, context->ctrl0); save_int8(buf, context->ctrl1); save_int8(buf, context->ar); save_int8(buf, context->ar_mask); save_int8(buf, context->triggered); save_int8(buf, context->sync_counter); } void lc8951_deserialize(deserialize_buffer *buf, void *vcontext) { lc8951 *context = vcontext; context->cycle = load_int32(buf); context->decode_end = load_int32(buf); context->transfer_end = load_int32(buf); context->next_byte_cycle = load_int32(buf); context->sector_counter = load_int16(buf); load_buffer8(buf, context->buffer, sizeof(context->buffer)); load_buffer8(buf, context->regs, sizeof(context->regs)); load_buffer8(buf, context->comin, sizeof(context->comin)); context->dac = load_int16(buf); context->comin_write = load_int8(buf); context->comin_count = load_int8(buf); context->ifctrl = load_int8(buf); context->ctrl0 = load_int8(buf); context->ctrl1 = load_int8(buf); context->ar = load_int8(buf); context->ar_mask = load_int8(buf); context->triggered = load_int8(buf); context->sync_counter = load_int8(buf); }