1
0
mirror of https://github.com/mist-devel/mist-firmware.git synced 2026-01-11 23:43:04 +00:00
2023-07-03 21:44:55 +02:00

649 lines
15 KiB
C

#include <string.h>
#include <stdio.h>
#include "pcecd.h"
#include "cue_parser.h"
#include "user_io.h"
#include "utils.h"
#include "debug.h"
// CDD command
#define PCECD_COMM_TESTUNIT 0x00
#define PCECD_COMM_REQUESTSENSE 0x03
#define PCECD_COMM_READ6 0x08
#define PCECD_COMM_MODESELECT6 0x15
#define PCECD_COMM_SAPSP 0xD8
#define PCECD_COMM_SAPEP 0xD9
#define PCECD_COMM_PAUSE 0xDA
#define PCECD_COMM_READSUBQ 0xDD
#define PCECD_COMM_GETDIRINFO 0xDE
#define PCECD_STATE_IDLE 0
#define PCECD_STATE_NODISC 1
#define PCECD_STATE_READ 2
#define PCECD_STATE_PLAY 3
#define PCECD_STATE_PAUSE 4
#define PCECD_STATUS_GOOD 0
#define PCECD_STATUS_CHECK_COND 1
#define PCECD_STATUS_CONDITION_MET 2
#define PCECD_STATUS_BUSY 4
#define PCECD_STATUS_INTERMEDIATE 8
#define SENSEKEY_NO_SENSE 0x0
#define SENSEKEY_NOT_READY 0x2
#define SENSEKEY_MEDIUM_ERROR 0x3
#define SENSEKEY_HARDWARE_ERROR 0x4
#define SENSEKEY_ILLEGAL_REQUEST 0x5
#define SENSEKEY_UNIT_ATTENTION 0x6
#define SENSEKEY_ABORTED_COMMAND 0xB
#define NSE_NO_DISC 0x0B
#define NSE_TRAY_OPEN 0x0D
#define NSE_SEEK_ERROR 0x15
#define NSE_HEADER_READ_ERROR 0x16
#define NSE_NOT_AUDIO_TRACK 0x1C
#define NSE_NOT_DATA_TRACK 0x1D
#define NSE_INVALID_COMMAND 0x20
#define NSE_INVALID_ADDRESS 0x21
#define NSE_INVALID_PARAMETER 0x22
#define NSE_END_OF_VOLUME 0x25
#define NSE_INVALID_REQUEST_IN_CDB 0x27
#define NSE_DISC_CHANGED 0x28
#define NSE_AUDIO_NOT_PLAYING 0x2C
#define PCECD_CDDAMODE_SILENT 0x00
#define PCECD_CDDAMODE_LOOP 0x01
#define PCECD_CDDAMODE_INTERRUPT 0x02
#define PCECD_CDDAMODE_NORMAL 0x03
// data io commands
#define CD_STAT_GET 0x60
#define CD_STAT_SEND 0x61
#define CD_COMMAND_GET 0x62
#define CD_DATA_GET 0x63
#define CD_DATA_SEND 0x64
#define CD_DATAOUT_REQ 0x65
#define CD_ACK 0x66
#define MAKE_STATUS(s,m) ((uint16_t)((((m)&0xFF) << 8) | ((s)&0xFF)))
typedef struct
{
uint8_t key;
uint8_t asc;
uint8_t ascq;
uint8_t fru;
} sense_t;
static sense_t sense;
typedef struct
{
uint32_t latency;
uint8_t state;
uint8_t isData;
int loaded;
int has_status;
char data_req;
char can_read_next;
char cdda_fifo_halffull;
int index;
int lba;
int cnt;
int scanOffset;
int audioLength;
int audioOffset;
int CDDAStart;
int CDDAEnd;
int CDDAFirst;
char CDDAMode;
uint16_t stat;
} pcecd_t;
static pcecd_t pcecdd;
static void CommandError(uint8_t key, uint8_t asc, uint8_t ascq, uint8_t fru) {
sense.key = key;
sense.asc = asc;
sense.ascq = ascq;
sense.fru = fru;
}
static void SendStatus(uint16_t status) {
pcecd_debugf("SendStatus: %d", status);
EnableFpga();
SPI(CD_STAT_SEND);
spi16(status);
DisableFpga();
}
static void PendStatus(uint16_t status) {
pcecd_debugf("PendStatus: %d", status);
pcecdd.stat = status;
pcecdd.has_status = 1;
}
static void SendData(char *buf, uint16_t len, unsigned char dm) {
EnableFpga();
SPI(CD_DATA_SEND);
SPI(len & 0xFF);
SPI(((len >> 8) & 0xFF) | (dm << 7));
spi_write(buf, len);
DisableFpga();
}
static void SendSector(uint16_t len, unsigned char dm) {
UINT br;
DISKLED_ON;
if (toc.tracks[pcecdd.index].type && (pcecdd.lba >= 0)) {
// data sector
if (toc.tracks[pcecdd.index].sector_size != 2048)
f_lseek(&toc.file->file, f_tell(&toc.file->file) + 16);
pcecd_debugf("Send data sector, lba: %d pos: %llu", pcecdd.lba, f_tell(&toc.file->file));
f_read(&toc.file->file, sector_buffer, 2048, &br);
if (toc.tracks[pcecdd.index].sector_size != 2048)
f_lseek(&toc.file->file, f_tell(&toc.file->file) + (toc.tracks[pcecdd.index].sector_size - 2048 - 16));
//pcecd_debugf("Send data sector, post pos: %llu", f_tell(&toc.file->file));
SendData(sector_buffer, 2048, dm);
//hexdump(buffer, 2048, 0);
} else {
f_read(&toc.file->file, sector_buffer, 2352, &br);
SendData(sector_buffer, 2352, dm);
}
DISKLED_OFF;
}
static char CheckDisk() {
if (!user_io_is_cue_mounted()) {
pcecdd.state = PCECD_STATE_NODISC;
CommandError(SENSEKEY_NOT_READY, NSE_NO_DISC, 0, 0);
SendStatus(MAKE_STATUS(PCECD_STATUS_CHECK_COND, 0));
return 0;
}
return 1;
}
static unsigned long pcecd_read_timer = 0;
static void pcecd_run() {
if (pcecdd.latency > 0) {
if(!CheckTimer(pcecd_read_timer)) return;
pcecd_read_timer = GetTimer(13);
pcecdd.latency--;
return;
}
if (pcecdd.state == PCECD_STATE_READ) {
if (pcecdd.index >= toc.last) {
pcecdd.state = PCECD_STATE_IDLE;
return;
}
if (!pcecdd.can_read_next)
return;
if (!user_io_is_cue_mounted()) {
pcecdd.state = PCECD_STATE_NODISC;
CommandError(SENSEKEY_NOT_READY, NSE_NO_DISC, 0, 0);
PendStatus(MAKE_STATUS(PCECD_STATUS_CHECK_COND, 0));
return;
}
if(!CheckTimer(pcecd_read_timer)) return;
pcecd_read_timer = GetTimer(13);
pcecdd.can_read_next = 0;
if (toc.tracks[pcecdd.index].type) {
// CD-ROM (Mode 1)
SendSector(2048, 1);
} else {
if (pcecdd.lba >= toc.tracks[pcecdd.index].start) {
pcecdd.isData = 0x00;
}
//SectorSend(0);
}
pcecdd.cnt--;
if (!pcecdd.cnt) {
PendStatus(MAKE_STATUS(PCECD_STATUS_GOOD, 0));
pcecdd.state = PCECD_STATE_IDLE;
}
pcecdd.lba++;
if (pcecdd.lba >=toc.tracks[pcecdd.index].end) {
pcecdd.index++;
pcecdd.isData = 0x01;
f_lseek(&toc.file->file, toc.tracks[pcecdd.index].offset);
}
} else if (pcecdd.state == PCECD_STATE_PLAY) {
if (!user_io_is_cue_mounted()) {
pcecdd.state = PCECD_STATE_NODISC;
CommandError(SENSEKEY_NOT_READY, NSE_NO_DISC, 0, 0);
PendStatus(MAKE_STATUS(PCECD_STATUS_CHECK_COND, 0));
return;
}
pcecdd.index = cue_gettrackbylba(pcecdd.lba);
if ((pcecdd.lba >= pcecdd.CDDAEnd) || toc.tracks[pcecdd.index].type || pcecdd.index >= toc.last)
{
if (pcecdd.CDDAMode == PCECD_CDDAMODE_LOOP) {
pcecdd.lba = pcecdd.CDDAStart;
pcecdd.latency = 2; // some time to seek back
}
else {
pcecdd.state = PCECD_STATE_IDLE;
}
if (pcecdd.CDDAMode == PCECD_CDDAMODE_INTERRUPT) {
SendStatus(MAKE_STATUS(PCECD_STATUS_GOOD, 0));
}
pcecd_debugf("playback reached the end %d\n", pcecdd.lba);
} else if (!pcecdd.cdda_fifo_halffull) {
for (int i = 0; i <= pcecdd.CDDAFirst; i++) {
if (!toc.tracks[pcecdd.index].type) {
f_lseek(&toc.file->file, toc.tracks[pcecdd.index].offset + (pcecdd.lba - toc.tracks[pcecdd.index].start) * 2352);
//pcecd_debugf("Audio sector send = %i, track = %i, offset = %llu", pcecdd.lba, pcecdd.index, f_tell(&toc.file->file));
SendSector(2352, 0);
}
pcecdd.lba++;
}
pcecdd.CDDAFirst = 0;
}
}
}
static void pcecd_command() {
int new_lba = 0;
msf_t msf;
int track;
uint8_t command[12];
uint8_t buf[32];
memset(buf, 0, 32);
int i;
EnableFpga();
SPI(CD_COMMAND_GET);
for (int i = 0; i < 12; i++)
command[i] = SPI(0);
DisableFpga();
//pcecd_debugf("command: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
// command[0], command[1], command[2], command[3], command[4], command[5],
// command[6], command[7], command[8], command[9], command[10], command[11]);
switch (command[0]) {
case PCECD_COMM_TESTUNIT:
if (CheckDisk()) {
SendStatus(MAKE_STATUS(PCECD_STATUS_GOOD, 0));
}
pcecd_debugf("Command TESTUNIT, state = %u", pcecdd.state);
break;
case PCECD_COMM_REQUESTSENSE:
buf[0] = 18;
buf[1] = 0 | 0x80;
buf[2] = 0x70;
buf[4] = sense.key;
buf[9] = 0x0A;
buf[14] = sense.asc;
buf[15] = sense.ascq;
buf[16] = sense.fru;
sense.key = sense.asc = sense.ascq = sense.fru = 0;
SendData(buf + 2, 18, 1);
pcecd_debugf("Command REQUESTSENSE, key = %02X, asc = %02X, ascq = %02X, fru = %02X", sense.key, sense.asc, sense.ascq, sense.fru);
SendStatus(MAKE_STATUS(PCECD_STATUS_GOOD, 0));
break;
case PCECD_COMM_GETDIRINFO: {
if (!CheckDisk()) break;
int len = 0;
switch (command[1]) {
case 0:
default:
buf[0] = 2;
buf[1] = 0 | 0x80;
buf[2] = 1;
buf[3] = bin2bcd(toc.last);
len = 2;
break;
case 1:
new_lba = toc.end + 150;
LBA2MSF(new_lba, &msf);
buf[0] = 4;
buf[1] = 0 | 0x80;
buf[2] = bin2bcd(msf.m);
buf[3] = bin2bcd(msf.s);
buf[4] = bin2bcd(msf.f);
buf[5] = 0;
len = 4;
break;
case 2:
track = bcd2bin(command[2]);
new_lba = toc.tracks[track - 1].start + 150;
LBA2MSF(new_lba, &msf);
buf[0] = 4;
buf[1] = 0 | 0x80;
buf[2] = bin2bcd(msf.m);
buf[3] = bin2bcd(msf.s);
buf[4] = bin2bcd(msf.f);
buf[5] = toc.tracks[track - 1].type << 2;
len = 4;
break;
}
SendData(buf + 2, len, 1);
pcecd_debugf("Command GETDIRINFO, [1] = %02X, [2] = %02X(%d)", command[1], command[2], command[2]);
pcecd_debugf("Send data, len = %u, [2] = %02X, [3] = %02X, [4] = %02X, [5] = %02X\n\x1b[0m", len, buf[2], buf[3], buf[4], buf[5]);
SendStatus(MAKE_STATUS(PCECD_STATUS_GOOD, 0));
}
break;
case PCECD_COMM_READ6: {
if (!CheckDisk()) break;
new_lba = ((command[1] << 16) | (command[2] << 8) | command[3]) & 0x1FFFFF;
int cnt_ = command[4] ? command[4] : 256;
pcecdd.index = cue_gettrackbylba(new_lba);
/* HuVideo streams by fetching 120 sectors at a time, taking advantage of the geometry
* of the disc to reduce/eliminate seek time */
if ((pcecdd.lba == new_lba) && (cnt_ == 120))
{
pcecdd.latency = 0;
}
/*
else if (command[13] & 0x80) // fast seek (OSD setting)
{
pcecdd.latency = 0;
} */
else
{
pcecdd.latency = 0;//(int)(get_cd_seek_ms(pcecdd.lba, new_lba)/13.33);
}
pcecd_debugf("seek time ticks: %d", pcecdd.latency);
pcecdd.lba = new_lba;
pcecdd.cnt = cnt_;
int offset = (new_lba - toc.tracks[pcecdd.index].start) * toc.tracks[pcecdd.index].sector_size + toc.tracks[pcecdd.index].offset;
f_lseek(&toc.file->file, offset);
pcecd_debugf("lba: %d index: %d, offset: %d", new_lba, pcecdd.index, offset);
pcecdd.audioOffset = 0;
pcecdd.can_read_next = 1;
pcecdd.state = PCECD_STATE_READ;
pcecd_debugf("Command READ6, lba = %u, cnt = %u", pcecdd.lba, pcecdd.cnt);
}
break;
case PCECD_COMM_MODESELECT6:
pcecd_debugf("Command MODESELECT6, cnt = %u", command[4]);
if (command[4]) {
pcecdd.data_req = 1;
}
else {
SendStatus(MAKE_STATUS(PCECD_STATUS_GOOD, 0));
}
break;
case PCECD_COMM_SAPSP: {
if (!CheckDisk()) break;
switch (command[9] & 0xc0)
{
default:
case 0x00:
new_lba = (command[3] << 16) | (command[4] << 8) | command[5];
break;
case 0x40:
new_lba = MSF2LBA(bcd2bin(command[2]), bcd2bin(command[3]), bcd2bin(command[4]));
break;
case 0x80:
{
int track = bcd2bin(command[2]);
if (!track)
track = 1;
else if (track > toc.last)
track = toc.last;
new_lba = toc.tracks[track - 1].start;
}
break;
}
/*
if (command[13] & 0x80) // fast seek (OSD setting)
{
pcecdd.latency = 0;
}
else*/
{
pcecdd.latency = 0;//(int)(get_cd_seek_ms(this->lba, new_lba) / 13.33);
}
pcecd_debugf("seek time ticks: %d", pcecdd.latency);
pcecdd.lba = new_lba;
int index = cue_gettrackbylba(new_lba);
pcecdd.index = index;
pcecdd.CDDAStart = new_lba;
pcecdd.CDDAEnd = toc.end;
pcecdd.CDDAMode = command[1];
pcecdd.CDDAFirst = 1;
if (pcecdd.CDDAMode == PCECD_CDDAMODE_SILENT) {
pcecdd.state = PCECD_STATE_PAUSE;
} else {
pcecdd.state = PCECD_STATE_PLAY;
}
PendStatus(MAKE_STATUS(PCECD_STATUS_GOOD, 0));
}
pcecd_debugf("Command SAPSP, start = %d, end = %d, [1] = %02X, [2] = %02X, [9] = %02X\n", pcecdd.CDDAStart, pcecdd.CDDAEnd, command[1], command[2], command[9]);
break;
case PCECD_COMM_SAPEP: {
if (!CheckDisk()) break;
switch (command[9] & 0xc0)
{
default:
case 0x00:
new_lba = (command[3] << 16) | (command[4] << 8) | command[5];
break;
case 0x40:
new_lba = MSF2LBA(bcd2bin(command[2]), bcd2bin(command[3]), bcd2bin(command[4]));
break;
case 0x80:
{
int track = bcd2bin(command[2]);
if (!track) track = 1;
new_lba = (track >= toc.last) ? toc.end : (toc.tracks[track - 1].start);
}
break;
}
pcecdd.CDDAMode = command[1];
pcecdd.CDDAEnd = new_lba;
if (pcecdd.CDDAMode == PCECD_CDDAMODE_SILENT) {
pcecdd.state = PCECD_STATE_IDLE;
} else {
pcecdd.state = PCECD_STATE_PLAY;
}
pcecd_debugf("Command SAPEP, end = %i, [1] = %02X, [2] = %02X, [9] = %02X", pcecdd.CDDAEnd, command[1], command[2], command[9]);
if (pcecdd.CDDAMode != PCECD_CDDAMODE_INTERRUPT) {
SendStatus(MAKE_STATUS(PCECD_STATUS_GOOD, 0));
}
}
break;
case PCECD_COMM_PAUSE:
if (!CheckDisk()) break;
pcecdd.state = PCECD_STATE_PAUSE;
SendStatus(MAKE_STATUS(PCECD_STATUS_GOOD, 0));
pcecd_debugf("Command PAUSE, current lba = %i\n", pcecdd.lba);
break;
case PCECD_COMM_READSUBQ: {
if (!CheckDisk()) break;
int lba_rel = pcecdd.lba - toc.tracks[pcecdd.index].start;
buf[0] = 0x0A;
buf[1] = 0 | 0x80;
buf[2] = pcecdd.state == PCECD_STATE_PAUSE ? 2 : (pcecdd.state == PCECD_STATE_PLAY ? 0 : 3);
buf[3] = 0;
buf[4] = bin2bcd(pcecdd.index + 1);
buf[5] = bin2bcd(pcecdd.index);
LBA2MSF(lba_rel, &msf);
buf[6] = bin2bcd(msf.m);
buf[7] = bin2bcd(msf.s);
buf[8] = bin2bcd(msf.f);
LBA2MSF(pcecdd.lba, &msf);
buf[9] = bin2bcd(msf.m);
buf[10] = bin2bcd(msf.s);
buf[11] = bin2bcd(msf.f);
SendData(buf + 2, 10, 1);
pcecd_debugf("Command READSUBQ, [1] = %02X, track = %i, index = %i, lba_rel = %i, lba_abs = %i\n\x1b[0m", command[1], pcecdd.index + 1, pcecdd.index, lba_rel, pcecdd.lba);
SendStatus(MAKE_STATUS(PCECD_STATUS_GOOD, 0));
}
break;
default:
CommandError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_COMMAND, 0, 0);
pcecd_debugf("Command undefined, [0] = %02X, [1] = %02X, [2] = %02X, [3] = %02X, [4] = %02X, [5] = %02X",
command[0], command[1], command[2], command[3], command[4], command[5]);
pcecdd.has_status = 0;
SendStatus(MAKE_STATUS(PCECD_STATUS_CHECK_COND, 0));
break;
}
}
static void pcecd_data() {
uint8_t data[10];
EnableFpga();
SPI(CD_DATA_GET);
for (int i = 0; i < 10; i++)
data[i] = SPI(0);
DisableFpga();
pcecd_debugf("data: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
data[0], data[1], data[2], data[3], data[4],
data[5], data[6], data[7], data[8], data[9]);
}
static void pcecd_clear_busy() {
//pcecd_debugf("Clear busy");
pcecdd.can_read_next = 1;
}
static void pcecd_reset() {
pcecd_debugf("Reset request");
pcecdd.latency = 0;
pcecdd.index = 0;
pcecdd.lba = 0;
pcecdd.scanOffset = 0;
pcecdd.isData = 1;
pcecdd.state = user_io_is_cue_mounted() ? PCECD_STATE_IDLE : PCECD_STATE_NODISC;
pcecdd.audioLength = 0;
pcecdd.audioOffset = 0;
pcecdd.has_status = 0;
pcecdd.data_req = 0;
pcecdd.can_read_next = 0;
pcecdd.CDDAStart = 0;
pcecdd.CDDAEnd = 0;
pcecdd.CDDAMode = PCECD_CDDAMODE_SILENT;
pcecdd.stat = 0;
pcecdd.cdda_fifo_halffull = 1;
}
static unsigned long pcecd_timer = 0;
void pcecd_poll() {
char c;
EnableFpga();
c = SPI(CD_STAT_GET); // cmd request
DisableFpga();
if (c&0x01) pcecd_command();
if (c&0x02) pcecd_data();
if (c&0x04) pcecd_clear_busy();
if (c&0x08) {
pcecd_reset();
EnableFpga();
SPI(CD_ACK);
SPI(0);
DisableFpga();
}
pcecdd.cdda_fifo_halffull = (c & 0x10);
pcecd_run();
if(CheckTimer(pcecd_timer)) {
pcecd_timer = GetTimer(13);
if (pcecdd.has_status && !pcecdd.latency) {
SendStatus(pcecdd.stat);
pcecdd.has_status = 0;
}
if (pcecdd.data_req) {
EnableFpga();
SPI(CD_DATAOUT_REQ);
SPI(0);
DisableFpga();
pcecdd.data_req = 0;
}
}
}