#include #include #include #include #include #include #include #include #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/device.h> #include <86box/io.h> #include <86box/pic.h> #include <86box/dma.h> #include <86box/cdrom.h> #include <86box/cdrom_interface.h> #include <86box/cdrom_mke.h> #include <86box/plat.h> #include <86box/sound.h> #include <86box/fifo8.h> /* https://elixir.bootlin.com/linux/2.0.29/source/include/linux/sbpcd.h CR-562-B is classified as Family1 in this driver, so uses the CMD1_ prefix. */ #define CDROM_STATUS_DOOR 0x80 #define CDROM_STATUS_DISC_IN 0x40 #define CDROM_STATUS_SPIN_UP 0x20 #define CDROM_STATUS_ERROR 0x10 #define CDROM_STATUS_DOUBLE_SPEED 0x02 #define CDROM_STATUS_READY 0x01 //Status returned from device #define STAT_READY 0x01 #define STAT_PLAY 0x08 #define STAT_ERROR 0x10 #define STAT_DISK 0x40 #define STAT_TRAY 0x80 //Seems Correct #define CMD1_PAUSERESUME 0x0D #define CMD1_RESET 0x0a #define CMD1_LOCK_CTL 0x0c #define CMD1_TRAY_CTL 0x07 #define CMD1_MULTISESS 0x8d #define CMD1_SUBCHANINF 0x11 #define CMD1_ABORT 0x08 //#define CMD1_PATH_CHECK 0x??? #define CMD1_SEEK 0x01 #define CMD1_READ 0x10 #define CMD1_SPINUP 0x02 #define CMD1_SPINDOWN 0x06 #define CMD1_READ_UPC 0x88 //#define CMD1_PLAY 0x??? #define CMD1_PLAY_MSF 0x0e #define CMD1_PLAY_TI 0x0f #define CMD1_STATUS 0x05 #define CMD1_READ_ERR 0x82 #define CMD1_READ_VER 0x83 #define CMD1_SETMODE 0x09 #define CMD1_GETMODE 0x84 #define CMD1_CAPACITY 0x85 #define CMD1_READSUBQ 0x87 #define CMD1_DISKINFO 0x8b #define CMD1_READTOC 0x8c #define CMD1_PAU_RES 0x0d #define CMD1_PACKET 0x8e #define CMD1_SESSINFO 0x8d typedef struct mke_t { bool tray_open; uint8_t enable_register; uint8_t command_buffer[7]; uint8_t command_buffer_pending; uint8_t data_select; uint8_t media_selected;//temporary hack Fifo8 data_fifo, info_fifo; Fifo8 errors_fifo; cdrom_t* cdrom_dev; uint32_t sector_type, sector_flags; uint32_t unit_attention; uint8_t cdbuffer[624240]; } mke_t; mke_t mke; #define mke_log(x, ...) #define CHECK_READY() { if (mke.cdrom_dev->image_path[0] == 0) { fifo8_push(&mke.errors_fifo, 0x03); return; } } static uint8_t temp_buf[65536]; void mke_get_subq(cdrom_t *dev,uint8_t *b) { //dev->ops->get_subchannel(dev, dev->seek_pos, &subc); //cdrom_get_current_subchannel(dev, &subc); //cdrom_get_current_subcodeq(dev, b); cdrom_get_current_subcodeq(dev, temp_buf); b[0] = 0x80; //? b[1] = ((temp_buf[0] & 0xf) << 4) | ((temp_buf[0] & 0xf0) >> 4); b[2] = temp_buf[1]; b[3] = temp_buf[2]; b[4] = temp_buf[6]; b[5] = temp_buf[7]; b[6] = temp_buf[8]; b[7] = temp_buf[3]; b[8] = temp_buf[4]; b[9] = temp_buf[5]; b[10]=0; //?? } uint8_t mke_read_toc(cdrom_t *dev, unsigned char *b, uint8_t track) { track_info_t ti; int first_track; int last_track; //dev->ops->get_tracks(dev, &first_track, &last_track); cdrom_read_toc(dev, temp_buf, CD_TOC_NORMAL, 0, 0, 65536); first_track = temp_buf[2]; last_track = temp_buf[3]; if(track > last_track) return 0; //should we allow +1 here? dev->ops->get_track_info(dev, track, 0, &ti); b[0]=0x0; b[1]=ti.attr; b[2]=ti.number; b[3]=0; b[4]=ti.m; b[5]=ti.s; b[6]=ti.f; b[7]=0; return 1; } uint8_t mke_disc_info(cdrom_t *dev, unsigned char *b) { track_info_t ti; int first_track; int last_track; cdrom_read_toc(dev, temp_buf, CD_TOC_NORMAL, 0, 0, 65536); first_track = temp_buf[2]; last_track = temp_buf[3]; dev->ops->get_track_info(dev, last_track+1, 0, &ti); b[0]=0x0; b[1]=first_track; b[2]=last_track; b[3]=ti.m; b[4]=ti.s; b[5]=ti.f; return 1; } uint8_t mke_disc_capacity(cdrom_t *dev, unsigned char *b) { track_info_t ti; int first_track; int last_track; //dev->ops->get_tracks(dev, &first_track, &last_track); cdrom_read_toc(dev, temp_buf, CD_TOC_NORMAL, 0, 0, 65536); first_track = temp_buf[2]; last_track = temp_buf[3]; dev->ops->get_track_info(dev, last_track+1, 0, &ti); b[0]=ti.m; b[1]=ti.s; b[2]=ti.f-1; //TODO THIS NEEDS TO HANDLE FRAME 0, JUST BEING LAZY 6AM b[3]=0x08; b[4]=0x00; return 1; } uint8_t mke_cdrom_status(cdrom_t *dev, mke_t* mke) { uint8_t status = 0; status |= 2;//this bit seems to always be set? //bit 4 never set? if(dev->cd_status == CD_STATUS_PLAYING) status |= STAT_PLAY; if(dev->cd_status == CD_STATUS_PAUSED) status |= STAT_PLAY; if(fifo8_num_used(&mke->errors_fifo)) status |= 0x10; status |= 0x20;//always set? status |= STAT_TRAY; if(dev->image_path[0] != 0) { status |= STAT_DISK; status |= STAT_READY; } return status; } void MKE_COMMAND(uint8_t value) { uint16_t i,len; uint8_t x[12];//this is wasteful handling of buffers for compatibility, but will optimize later. subchannel_t subc; int old_cd_status; if(mke.command_buffer_pending) { mke.command_buffer[6-mke.command_buffer_pending+1]=value; mke.command_buffer_pending--; } if(mke.command_buffer[0] == CMD1_ABORT) { mke_log("CMD_ABORT\n"); //fifo8_reset(&mke.info_fifo); fifo8_reset(&mke.info_fifo); mke.command_buffer[0]=0; mke.command_buffer_pending=7; //fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); } if (!mke.command_buffer_pending && mke.command_buffer[0]) { switch (mke.command_buffer[0]) { case 06: { fifo8_reset(&mke.info_fifo); cdrom_stop(mke.cdrom_dev); cdrom_eject(mke.cdrom_dev->id); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; } case 07: { fifo8_reset(&mke.info_fifo); cdrom_reload(mke.cdrom_dev->id); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; } case CMD1_READ: { //cdrom_readsector_raw(mke.cdrom_dev, ) uint32_t count = mke.command_buffer[6]; uint8_t* buf = mke.cdbuffer; int len = 0; int res = 0; int error = 0; uint64_t lba = MSFtoLBA(mke.command_buffer[1], mke.command_buffer[2], mke.command_buffer[3]); CHECK_READY(); while (count) { if ((res = cdrom_readsector_raw(mke.cdrom_dev, mke.cdbuffer, lba, 0, mke.sector_type, mke.sector_flags, &len, 0)) > 0) { fifo8_push_all(&mke.data_fifo, buf, mke.cdrom_dev->sector_size); lba++; buf += mke.cdrom_dev->sector_size; } else { fifo8_push(&mke.errors_fifo, res == 0 ? 0x10 : 0x05); break; } count--; } if (count != 0) { fifo8_reset(&mke.data_fifo); } break; } case CMD1_READSUBQ: CHECK_READY(); mke_get_subq(mke.cdrom_dev, (uint8_t *)&x); fifo8_reset(&mke.info_fifo); //fifo8_push_all(&cdrom.info_fifo, x, 11); fifo8_push_all(&mke.info_fifo, x, 11); /* for(i=0;i<11;i++) { cdrom_fifo_write(&cdrom.info_fifo,x[i]); } */ fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_SETMODE: //Returns 1 fifo8_reset(&mke.info_fifo); mke_log("CMD: SET MODE:"); for(i=0;i<6;i++) { mke_log("%02x ",mke.command_buffer[i+1]); } mke_log("\n"); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_GETMODE://6 mke_log("GET MODE\n"); uint8_t mode[5] = {[1] = 0x08}; fifo8_push_all(&mke.info_fifo, mode, 5); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_PAUSERESUME: CHECK_READY(); cdrom_audio_pause_resume(mke.cdrom_dev, mke.command_buffer[1] >> 7); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_CAPACITY://6 mke_log("DISK CAPACITY\n"); CHECK_READY(); mke_disc_capacity(mke.cdrom_dev, (uint8_t *)&x); //fifo8_push_all(&cdrom.info_fifo, x, 5); fifo8_push_all(&mke.info_fifo, x, 5); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_DISKINFO: //7 mke_log("DISK INFO\n"); CHECK_READY(); mke_disc_info(mke.cdrom_dev, (uint8_t *)&x); fifo8_push_all(&mke.info_fifo, x, 6); /* for(i=0;i<6;i++) { mke_log("%02x ",x[i]); cdrom_fifo_write(&cdrom.info_fifo,x[i]); } mke_log("\n"); */ fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_READTOC: CHECK_READY(); fifo8_reset(&mke.info_fifo); /* mke_log("READ TOC:"); for(i=0;i<6;i++) { mke_log("%02x ",mke.command_buffer[i+1]); } mke_log(" | "); */ mke_read_toc(mke.cdrom_dev,(uint8_t *)&x,mke.command_buffer[2]); fifo8_push_all(&mke.info_fifo, x, 8); /* for(i=0;i<8;i++) { mke_log("%02x ",x[i]); cdrom_fifo_write(&cdrom.info_fifo,x[i]); } */ /* mke_log("\n"); */ fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_PLAY_MSF: CHECK_READY(); fifo8_reset(&mke.info_fifo); mke_log("PLAY MSF:"); for(i=0;i<6;i++) { mke_log("%02x ",mke.command_buffer[i+1]); } mke_log("\n"); //cdrom_audio_playmsf(&cdrom, // mke.command_buffer[1], // mke.command_buffer[2], // mke.command_buffer[3], // mke.command_buffer[4], // mke.command_buffer[5], // mke.command_buffer[6] //); { int msf = 1; int pos = (mke.command_buffer[1] << 16) | (mke.command_buffer[2] << 8) | mke.command_buffer[3]; int len = (mke.command_buffer[4] << 16) | (mke.command_buffer[5] << 8) | mke.command_buffer[6]; if (!cdrom_audio_play(mke.cdrom_dev, pos, len, msf)) { fifo8_push(&mke.errors_fifo, 0x0E); fifo8_push(&mke.errors_fifo, 0x10); } } fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_SEEK: CHECK_READY(); old_cd_status = mke.cdrom_dev->cd_status; fifo8_reset(&mke.info_fifo); mke_log("SEEK MSF:"); //TODO: DOES THIS IMPACT CURRENT PLAY LENGTH? for(i=0;i<6;i++) { mke_log("%02x ",mke.command_buffer[i+1]); } cdrom_stop(mke.cdrom_dev); cdrom_seek(mke.cdrom_dev, (mke.command_buffer[1] << 16) | (mke.command_buffer[2] << 8) | mke.command_buffer[3], 0); if (old_cd_status == CD_STATUS_PLAYING || old_cd_status == CD_STATUS_PAUSED) { cdrom_audio_play(mke.cdrom_dev, mke.cdrom_dev->seek_pos, 0, -1); cdrom_audio_pause_resume(mke.cdrom_dev, old_cd_status == CD_STATUS_PLAYING); } fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_SESSINFO: CHECK_READY(); fifo8_reset(&mke.info_fifo); mke_log("CMD: READ SESSION INFO\n"); uint8_t session_info[6] = {0}; fifo8_push_all(&mke.info_fifo, session_info, 6); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_READ_UPC: CHECK_READY(); fifo8_reset(&mke.info_fifo); mke_log("CMD: READ UPC\n"); uint8_t upc[8] = {[0] = 80}; fifo8_push_all(&mke.info_fifo, upc, 8); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_READ_ERR: fifo8_reset(&mke.info_fifo); mke_log("CMD: READ ERR\n"); //cdrom_read_errors(&cdrom,(uint8_t *)x); memset(x, 0, 8); if (fifo8_num_used(&mke.errors_fifo)) { fifo8_pop_buf(&mke.errors_fifo, x, fifo8_num_used(&mke.errors_fifo)); } fifo8_push_all(&mke.info_fifo, x, 8); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); fifo8_reset(&mke.errors_fifo); break; case CMD1_READ_VER: /* SB2CD Expects 12 bytes, but drive only returns 11. */ fifo8_reset(&mke.info_fifo); mke_log("CMD: READ VER\n"); uint8_t ver[10] = "CR-5631.02"; fifo8_push_all(&mke.info_fifo, ver, 10); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; case CMD1_STATUS: fifo8_reset(&mke.info_fifo); fifo8_push(&mke.info_fifo, mke_cdrom_status(mke.cdrom_dev, &mke)); break; default: mke_log("MKE: Unknown Commnad [%02x]\n",mke.command_buffer[0]); } } else if (!mke.command_buffer_pending) { //we are done byt not in a command. should we make sure it is a valid command here? mke.command_buffer[0]=value; mke.command_buffer_pending=6; } } void MKE_WRITE(uint16_t address, uint8_t value, void* priv) { pclog("MKEWRITE: 0x%X, 0x%02X\n", address & 0xf, value); if(mke.enable_register && ((address & 0xF) != 3)) { //mke_log("Ignore Write Unit %u\n",mke.enable_register); return; } //mke_log("MKE WRITE: %02x => %03x\n",value,address); switch(address & 0xF) { case 0: MKE_COMMAND(value); break; case 1: mke.data_select=value; break; case 3: mke.enable_register=value; break; default: pclog("w %03x %02x\n", address, value); break; } } uint8_t MKE_READ(uint16_t address, void* priv) { uint8_t x; if(mke.enable_register ) { pclog("Ignore Read Unit %u\n",mke.enable_register); return 0; } pclog("MKEREAD: 0x%X\n", address & 0xf); switch(address & 0xF) { case 0://Info if(mke.data_select) { return fifo8_pop(&mke.data_fifo); //cdrom_fifo_read(&cdrom.data_fifo); } else { return fifo8_pop(&mke.info_fifo); //return cdrom_fifo_read(&cdrom.info_fifo); } break; case 1://Status /* 1 = Status Change 2 = Data Ready 4 = Response Ready 8 = Attention / Issue ? */ x=0xFF; //if(cdrom.media_changed) x ^= 1; if(fifo8_num_used(&mke.data_fifo)) x ^= 2;//DATA FIFO if(fifo8_num_used(&mke.info_fifo)) x ^= 4;//STATUS FIFO if(fifo8_num_used(&mke.errors_fifo)) x ^=8; return x; break; case 2://Data return fifo8_pop(&mke.data_fifo); case 3: return mke.enable_register; break; default: /* mke_log("MKE Unknown Read Address: %03x\n",address); */ pclog("MKE Unknown Read Address: %03x\n",address); break; } return 0xff; } void mke_close(void* priv) { fifo8_destroy(&mke.info_fifo); fifo8_destroy(&mke.data_fifo); fifo8_destroy(&mke.errors_fifo); } static void mke_cdrom_insert(void *priv) { mke_t *dev = (mke_t *)priv; if ((dev == NULL) || (dev->cdrom_dev == NULL)) return; if (dev->cdrom_dev->ops == NULL) { dev->unit_attention = 0; dev->cdrom_dev->cd_status = CD_STATUS_EMPTY; fifo8_push(&dev->errors_fifo, 0x11); } else if (dev->cdrom_dev->cd_status & CD_STATUS_TRANSITION) { dev->unit_attention = 1; /* Turn off the medium changed status. */ dev->cdrom_dev->cd_status &= ~CD_STATUS_TRANSITION; fifo8_push(&dev->errors_fifo, 0x11); } else { dev->unit_attention = 0; dev->cdrom_dev->cd_status |= CD_STATUS_TRANSITION; } } void* mke_init(const device_t* info) { cdrom_t* dev; memset(&mke, 0, sizeof(mke_t)); for (int i = 0; i < CDROM_NUM; i++) { if (cdrom[i].bus_type == CDROM_BUS_MKE) { dev = &cdrom[i]; break; } } if (!dev) return NULL; fifo8_create(&mke.info_fifo, 128); fifo8_create(&mke.data_fifo, 624240); fifo8_create(&mke.errors_fifo, 8); mke.cdrom_dev = dev; mke.command_buffer_pending = 7; mke.sector_type = 0x08 | (1 << 4); mke.sector_flags = 0x10; dev->priv = &mke; dev->insert = mke_cdrom_insert; dev->cached_sector = -1; dev->sector_size = 2048; io_sethandler(0x250, 16, MKE_READ, NULL, NULL, MKE_WRITE, NULL, NULL, &mke); return &mke; } const device_t mke_cdrom_device = { .name = "Panasonic/MKE CD-ROM interface", .internal_name = "mkecd", .flags = DEVICE_ISA16, .local = 0, .init = mke_init, .close = mke_close, .reset = NULL, .available = NULL, .speed_changed = NULL, .force_redraw = NULL, .config = NULL };