ref: cb7d802d87208da493cc31a0ea9fab22f4472637
dir: /psx/dev/cdrom/cdrom.c/
#include "p9.h"
#include "dev/cdrom/cdrom.h"
#include "dev/ic.h"
typedef void (*cdrom_cmd_func)(psx_cdrom_t* cdrom);
void cdrom_cmd_getstat(psx_cdrom_t* cdrom);
void cdrom_cmd_setloc(psx_cdrom_t* cdrom);
void cdrom_cmd_play(psx_cdrom_t* cdrom);
void cdrom_cmd_forward(psx_cdrom_t* cdrom);
void cdrom_cmd_backward(psx_cdrom_t* cdrom);
void cdrom_cmd_readn(psx_cdrom_t* cdrom);
void cdrom_cmd_motoron(psx_cdrom_t* cdrom);
void cdrom_cmd_stop(psx_cdrom_t* cdrom);
void cdrom_cmd_pause(psx_cdrom_t* cdrom);
void cdrom_cmd_init(psx_cdrom_t* cdrom);
void cdrom_cmd_mute(psx_cdrom_t* cdrom);
void cdrom_cmd_demute(psx_cdrom_t* cdrom);
void cdrom_cmd_setfilter(psx_cdrom_t* cdrom);
void cdrom_cmd_setmode(psx_cdrom_t* cdrom);
void cdrom_cmd_getparam(psx_cdrom_t* cdrom);
void cdrom_cmd_getlocl(psx_cdrom_t* cdrom);
void cdrom_cmd_getlocp(psx_cdrom_t* cdrom);
void cdrom_cmd_setsession(psx_cdrom_t* cdrom);
void cdrom_cmd_gettn(psx_cdrom_t* cdrom);
void cdrom_cmd_gettd(psx_cdrom_t* cdrom);
void cdrom_cmd_seekl(psx_cdrom_t* cdrom);
void cdrom_cmd_seekp(psx_cdrom_t* cdrom);
void cdrom_cmd_test(psx_cdrom_t* cdrom);
void cdrom_cmd_getid(psx_cdrom_t* cdrom);
void cdrom_cmd_reads(psx_cdrom_t* cdrom);
void cdrom_cmd_reset(psx_cdrom_t* cdrom);
void cdrom_cmd_getq(psx_cdrom_t* cdrom);
void cdrom_cmd_readtoc(psx_cdrom_t* cdrom);
void cdrom_cmd_videocd(psx_cdrom_t* cdrom);
cdrom_cmd_func cdrom_cmd_table[] = {
(cdrom_cmd_func)0,
cdrom_cmd_getstat,
cdrom_cmd_setloc,
cdrom_cmd_play,
cdrom_cmd_forward,
cdrom_cmd_backward,
cdrom_cmd_readn,
cdrom_cmd_motoron,
cdrom_cmd_stop,
cdrom_cmd_pause,
cdrom_cmd_init,
cdrom_cmd_mute,
cdrom_cmd_demute,
cdrom_cmd_setfilter,
cdrom_cmd_setmode,
cdrom_cmd_getparam,
cdrom_cmd_getlocl,
cdrom_cmd_getlocp,
cdrom_cmd_setsession,
cdrom_cmd_gettn,
cdrom_cmd_gettd,
cdrom_cmd_seekl,
cdrom_cmd_seekp,
(cdrom_cmd_func)0,
(cdrom_cmd_func)0,
cdrom_cmd_test,
cdrom_cmd_getid,
cdrom_cmd_reads,
cdrom_cmd_reset,
cdrom_cmd_getq,
cdrom_cmd_readtoc,
cdrom_cmd_videocd
};
static const char* cdrom_cmd_names[] = {
"<unimplemented>",
"CdlGetstat",
"CdlSetloc",
"CdlPlay",
"CdlForward",
"CdlBackward",
"CdlReadn",
"CdlMotoron",
"CdlStop",
"CdlPause",
"CdlInit",
"CdlMute",
"CdlDemute",
"CdlSetfilter",
"CdlSetmode",
"CdlGetparam",
"CdlGetlocl",
"CdlGetlocp",
"CdlSetsession",
"CdlGettn",
"CdlGettd",
"CdlSeekl",
"CdlSeekp",
"<unimplemented>",
"<unimplemented>",
"CdlTest",
"CdlGetid",
"CdlReads",
"CdlReset",
"CdlGetq",
"CdlReadtoc",
"CdlVideocd"
};
void cdrom_write_stat(psx_cdrom_t* cdrom, uint8_t data);
void cdrom_write_cmd(psx_cdrom_t* cdrom, uint8_t data);
void cdrom_write_null(psx_cdrom_t* cdrom, uint8_t data);
void cdrom_write_parm(psx_cdrom_t* cdrom, uint8_t data);
void cdrom_write_ier(psx_cdrom_t* cdrom, uint8_t data);
void cdrom_write_ifr(psx_cdrom_t* cdrom, uint8_t data);
void cdrom_write_req(psx_cdrom_t* cdrom, uint8_t data);
void cdrom_write_vol0(psx_cdrom_t* cdrom, uint8_t data);
void cdrom_write_vol1(psx_cdrom_t* cdrom, uint8_t data);
void cdrom_write_vol2(psx_cdrom_t* cdrom, uint8_t data);
void cdrom_write_vol3(psx_cdrom_t* cdrom, uint8_t data);
void cdrom_write_vapp(psx_cdrom_t* cdrom, uint8_t data);
psx_cdrom_t* psx_cdrom_create(void) {
return malloc(sizeof(psx_cdrom_t));
}
void psx_cdrom_init(psx_cdrom_t* cdrom, psx_ic_t* ic) {
memset(cdrom, 0, sizeof(psx_cdrom_t));
cdrom->io_base = PSX_CDROM_BEGIN;
cdrom->io_size = PSX_CDROM_SIZE;
cdrom->data = queue_create();
cdrom->response = queue_create();
cdrom->parameters = queue_create();
cdrom->ic = ic;
queue_init(cdrom->data, CD_SECTOR_SIZE);
queue_init(cdrom->response, 32);
queue_init(cdrom->parameters, 32);
cdrom->version = CDR_VERSION_C0A;
cdrom->region = CDR_REGION_AMERICA;
cdrom->vol[0] = 0x80;
cdrom->vol[1] = 0x00;
cdrom->vol[2] = 0x80;
cdrom->vol[3] = 0x00;
cdrom->pending_lba = 150;
cdrom->lba = 150;
cdrom->seek_precision = 1;
cdrom->fake_getlocl_data = 1;
}
void psx_cdrom_reset(psx_cdrom_t* cdrom) {
queue_clear(cdrom->data);
queue_clear(cdrom->response);
queue_clear(cdrom->parameters);
cdrom->prev_state = CD_STATE_IDLE;
cdrom->state = CD_STATE_IDLE;
cdrom->pending_command = 0;
cdrom->busy = 0;
cdrom->cdda_playing = 0;
cdrom->xa_playing = 0;
cdrom->read_ongoing = 0;
queue_clear(cdrom->data);
queue_clear(cdrom->response);
queue_clear(cdrom->parameters);
cdrom->vol[0] = 0x80;
cdrom->vol[1] = 0x00;
cdrom->vol[2] = 0x80;
cdrom->vol[3] = 0x00;
}
void psx_cdrom_set_version(psx_cdrom_t* cdrom, int version) {
cdrom->version = version;
}
void psx_cdrom_set_region(psx_cdrom_t* cdrom, int region) {
cdrom->region = region;
}
int psx_cdrom_open(psx_cdrom_t* cdrom, const char* path) {
if (!path)
return 1;
cdrom_cmd_reset(cdrom);
cdrom->disc = psx_disc_create();
cdrom->disc_type = psx_disc_open(cdrom->disc, path);
if (cdrom->disc_type == CDT_ERROR) {
psx_cdrom_close(cdrom);
return 0;
}
return 1;
}
void psx_cdrom_close(psx_cdrom_t* cdrom) {
if (cdrom->disc) {
psx_disc_destroy(cdrom->disc);
cdrom->disc = NULL;
}
}
void cdrom_process_setloc(psx_cdrom_t* cdrom) {
if (!cdrom->pending_lba)
return;
cdrom->lba = cdrom->pending_lba;
cdrom->pending_lba = 0;
}
void cdrom_set_int(psx_cdrom_t* cdrom, int n) {
cdrom->ifr = n;
}
int cdrom_get_read_delay(psx_cdrom_t* cdrom) {
return (cdrom->mode & MODE_SPEED) ? CD_DELAY_READ_DS : CD_DELAY_READ_SS;
}
int cdrom_get_pause_delay(psx_cdrom_t* cdrom) {
if (!cdrom->read_ongoing)
return 7000;
return CD_DELAY_1MS * ((cdrom->mode & MODE_SPEED) ? 35 : 70);
}
int cdrom_get_seek_delay(psx_cdrom_t* cdrom, int ts) {
int delay = cdrom->pending_speed_switch_delay;
cdrom->pending_speed_switch_delay = 0;
// Ridiculous delays for seeking to an audio sector
// or out of the disk
if (ts == TS_FAR) delay = 650 * CD_DELAY_1MS;
if (ts == TS_AUDIO) delay = 4000 * CD_DELAY_1MS;
// if (ts == TS_PREGAP) delay = 4000 * CD_DELAY_1MS;
return delay;
}
void cdrom_error(psx_cdrom_t* cdrom, uint8_t stat, uint8_t err) {
cdrom->ifr = 5;
queue_reset(cdrom->parameters);
queue_reset(cdrom->response);
if ((stat & CD_STAT_IDERROR) || (stat & CD_STAT_SEEKERROR)) {
queue_push(cdrom->response, stat);
} else {
queue_push(cdrom->response, CD_STAT_ERROR | stat);
}
queue_push(cdrom->response, err);
cdrom->prev_state = CD_STATE_IDLE;
cdrom->state = CD_STATE_IDLE;
cdrom->pending_command = 0;
cdrom->busy = 0;
}
void cdrom_handle_resp1(psx_cdrom_t* cdrom) {
cdrom->busy = 0;
// Check for no disc, some commands can be issued with no disc
// in the drive (e.g. Test, Setmode, Init, etc.)
// i.e. INT5(11h, 80h)
if (!cdrom->disc) {
switch (cdrom->pending_command) {
case CDL_SETLOC:
case CDL_PLAY:
case CDL_FORWARD:
case CDL_BACKWARD:
case CDL_READN:
case CDL_MOTORON:
case CDL_STOP:
case CDL_PAUSE:
case CDL_MUTE:
case CDL_DEMUTE:
case CDL_SETFILTER:
case CDL_GETLOCL:
case CDL_GETLOCP:
case CDL_SETSESSION:
case CDL_GETTN:
case CDL_GETTD:
case CDL_SEEKL:
case CDL_SEEKP:
case CDL_GETID:
case CDL_READS:
case CDL_GETQ: {
cdrom_error(cdrom, CD_STAT_SHELLOPEN, CD_ERR_NO_DISC);
return;
} break;
}
}
// Check for version-specific unsupported commands
switch (cdrom->pending_command) {
case CDL_RESET: {
if (cdrom->version == CDR_VERSION_01) {
cdrom_error(cdrom,
CD_STAT_SPINDLE,
CD_ERR_INVALID_COMMAND
);
return;
}
} break;
case CDL_GETQ:
case CDL_READTOC: {
if (cdrom->version < CDR_VERSION_C1A) {
cdrom_error(cdrom,
CD_STAT_SPINDLE,
CD_ERR_INVALID_COMMAND
);
return;
}
} break;
}
// Check for wrong number of parameters and invalid commands
// i.e. INT5(03h, 20h), INT5(03h, 40h)
switch (cdrom->pending_command) {
case CDL_GETSTAT:
case CDL_FORWARD:
case CDL_BACKWARD:
case CDL_READN:
case CDL_MOTORON:
case CDL_STOP:
case CDL_PAUSE:
case CDL_INIT:
case CDL_MUTE:
case CDL_DEMUTE:
case CDL_GETPARAM:
case CDL_GETLOCL:
case CDL_GETLOCP:
case CDL_GETTN:
case CDL_SEEKL:
case CDL_SEEKP:
case CDL_GETID:
case CDL_READS:
case CDL_RESET:
case CDL_READTOC: {
// These commands take no parameters
if (queue_size(cdrom->parameters)) {
cdrom_error(cdrom,
CD_STAT_SPINDLE,
CD_ERR_WRONG_PARAMETER_COUNT
);
return;
}
} break;
case CDL_SETLOC: {
// Setloc takes exactly 3 parameters
if (queue_size(cdrom->parameters) != 3) {
cdrom_error(cdrom,
CD_STAT_SPINDLE,
CD_ERR_WRONG_PARAMETER_COUNT
);
return;
}
} break;
case CDL_PLAY: {
// Play may take either 0 or 1 parameter
if (queue_size(cdrom->parameters) > 1) {
cdrom_error(cdrom,
CD_STAT_SPINDLE,
CD_ERR_WRONG_PARAMETER_COUNT
);
return;
}
} break;
case CDL_SETFILTER:
case CDL_GETQ: {
// Setfilter and GetQ both take exactly 2 parameters
if (queue_size(cdrom->parameters) != 2) {
cdrom_error(cdrom,
CD_STAT_SPINDLE,
CD_ERR_WRONG_PARAMETER_COUNT
);
return;
}
} break;
case CDL_SETMODE:
case CDL_GETTD:
case CDL_TEST: {
// Setmode and GetTD both take exactly 1 parameter
// Test may actually take additional parameters depending
// on the subfunction, but we only emulate subfunction 20h
// for now, which takes no extra parameters
if (queue_size(cdrom->parameters) != 1) {
cdrom_error(cdrom,
CD_STAT_SPINDLE,
CD_ERR_WRONG_PARAMETER_COUNT
);
return;
}
} break;
case CDL_VIDEOCD: {
// To-do: Check for model
// VideoCD is only supported on the SCPH-5903
// Should return invalid command normally
// VideoCD takes exactly 6 parameters
if (queue_size(cdrom->parameters) != 6) {
cdrom_error(cdrom,
CD_STAT_SPINDLE,
CD_ERR_WRONG_PARAMETER_COUNT
);
return;
}
} break;
default: {
// Invalid command
cdrom_error(cdrom,
CD_STAT_SPINDLE,
CD_ERR_INVALID_COMMAND
);
return;
} break;
}
// If everything is alright (i.e. disc present, valid command,
// correct number of parameters) then send "execute command"
cdrom_cmd_table[cdrom->pending_command](cdrom);
}
uint8_t cdrom_get_stat(psx_cdrom_t* cdrom) {
return ((cdrom->cdda_playing) ? CD_STAT_PLAY : 0) |
((cdrom->read_ongoing) ? CD_STAT_READ : 0) |
((cdrom->mode & 0x10) ? CD_STAT_IDERROR : 0) |
((!cdrom->disc) ? CD_STAT_SHELLOPEN : 0) |
CD_STAT_SPINDLE;
}
void cdrom_handle_read(psx_cdrom_t* cdrom) {
cdrom_process_setloc(cdrom);
int ts = psx_disc_query(cdrom->disc, cdrom->lba);
if (ts == TS_FAR) {
cdrom_error(cdrom,
CD_STAT_SPINDLE | CD_STAT_SEEKERROR,
CD_ERR_INVALID_SUBFUNCTION
);
return;
}
//if (ts == TS_AUDIO || ts == TS_PREGAP) {
if (ts == TS_AUDIO) {
cdrom_error(cdrom,
CD_STAT_SPINDLE | CD_STAT_SEEKERROR,
CD_ERR_SEEK_FAILED
);
return;
}
cdrom_set_int(cdrom, 1);
queue_push(cdrom->response, cdrom_get_stat(cdrom));
// Read sector into our data FIFO
psx_disc_read(cdrom->disc, cdrom->lba, cdrom->data->buf);
int size_bit = cdrom->mode & MODE_SECTOR_SIZE;
cdrom->data->read_index = size_bit ? 12 : 24;
cdrom->data->write_index = size_bit ? 0x924 : 0x800;
cdrom->data->write_index += cdrom->data->read_index;
cdrom->pending_lba = cdrom->lba + 1;
cdrom->delay = cdrom_get_read_delay(cdrom);
// printf("size=%x off=%u lba=%d: %02x:%02x:%02x delay=%u\n",
// cdrom->data->write_index,
// cdrom->data->read_index,
// cdrom->lba,
// cdrom->data->buf[0xc],
// cdrom->data->buf[0xd],
// cdrom->data->buf[0xe],
// cdrom->data->buf[0xf],
// cdrom->delay
// );
}
void psx_cdrom_update(psx_cdrom_t* cdrom, int cycles) {
if (cdrom->delay > 0) {
cdrom->delay -= cycles;
if (cdrom->delay > 0)
return;
}
cdrom->delay = 0;
if (cdrom->state == CD_STATE_IDLE)
return;
if (cdrom->state == CD_STATE_PLAY)
return;
// Hold IRQ until IFR is acknowledged
if (cdrom->ifr & 0x1f) {
cdrom->delay = 2;
return;
}
switch (cdrom->state) {
case CD_STATE_TX_RESP1: {
cdrom_handle_resp1(cdrom);
switch (cdrom->pending_command) {
case CDL_READN:
case CDL_READS:
break;
}
// Switching to read mode after executing a command
// has a 500ms penalty
if (cdrom->state == CD_STATE_READ) {
cdrom_process_setloc(cdrom);
cdrom->state = CD_STATE_READ;
cdrom->prev_state = CD_STATE_READ;
cdrom->delay = CD_DELAY_ONGOING_READ;
}
} break;
case CD_STATE_TX_RESP2: {
cdrom_cmd_table[cdrom->pending_command](cdrom);
// Switching to read mode after executing a command
// has a 500ms penalty
if (cdrom->state == CD_STATE_READ) {
cdrom_process_setloc(cdrom);
cdrom->state = CD_STATE_READ;
cdrom->prev_state = CD_STATE_READ;
cdrom->delay = CD_DELAY_ONGOING_READ;
}
} break;
case CD_STATE_READ: {
cdrom_handle_read(cdrom);
} break;
}
if ((cdrom->ifr & cdrom->ier) == 0)
return;
psx_ic_irq(cdrom->ic, IC_CDROM);
}
uint8_t cdrom_read_status(psx_cdrom_t* cdrom) {
int data_empty = queue_is_empty(cdrom->data) || !cdrom->data_req;
return (cdrom->index << 0) |
(cdrom->xa_playing << 2) |
(queue_is_empty(cdrom->parameters) << 3) |
((!queue_is_full(cdrom->parameters)) << 4) |
((!queue_is_empty(cdrom->response)) << 5) |
((!data_empty) << 6) |
(cdrom->busy << 7);
}
uint8_t cdrom_read_data(psx_cdrom_t* cdrom) {
if (!cdrom->data_req)
return 0;
// printf("read=%x write=%x data=%02x |%c|\n",
// cdrom->data->read_index,
// cdrom->data->write_index,
// queue_peek(cdrom->data),
// isprint(queue_peek(cdrom->data)) ? queue_peek(cdrom->data) : '.'
// );
return queue_pop(cdrom->data);
}
uint32_t psx_cdrom_read8(psx_cdrom_t* cdrom, uint32_t addr) {
switch (addr) {
case 0: return cdrom_read_status(cdrom);
case 1: return queue_pop(cdrom->response);
case 2: return cdrom_read_data(cdrom);
case 3: return (cdrom->index & 1) ? (0xe0 | cdrom->ifr) : cdrom->ier;
}
return 0;
}
void psx_cdrom_write8(psx_cdrom_t* cdrom, uint32_t addr, uint32_t value) {
switch ((cdrom->index << 2) | addr) {
case 0: cdrom_write_stat(cdrom, value); break;
case 1: cdrom_write_cmd(cdrom, value); break;
case 2: cdrom_write_parm(cdrom, value); break;
case 3: cdrom_write_req(cdrom, value); break;
case 4: cdrom_write_stat(cdrom, value); break;
case 5: cdrom_write_null(cdrom, value); break;
case 6: cdrom_write_ier(cdrom, value); break;
case 7: cdrom_write_ifr(cdrom, value); break;
case 8: cdrom_write_stat(cdrom, value); break;
case 9: cdrom_write_null(cdrom, value); break;
case 10: cdrom_write_vol0(cdrom, value); break;
case 11: cdrom_write_vol1(cdrom, value); break;
case 12: cdrom_write_stat(cdrom, value); break;
case 13: cdrom_write_vol2(cdrom, value); break;
case 14: cdrom_write_vol3(cdrom, value); break;
case 15: cdrom_write_vapp(cdrom, value); break;
}
}
void psx_cdrom_destroy(psx_cdrom_t* cdrom) {
psx_cdrom_close(cdrom);
queue_destroy(cdrom->data);
queue_destroy(cdrom->response);
queue_destroy(cdrom->parameters);
free(cdrom);
}
void cdrom_write_stat(psx_cdrom_t* cdrom, uint8_t data) {
cdrom->index = data & 3;
}
void cdrom_write_cmd(psx_cdrom_t* cdrom, uint8_t data) {
cdrom->prev_state = cdrom->state;
cdrom->state = CD_STATE_TX_RESP1;
cdrom->pending_command = data;
switch (cdrom->pending_command) {
case CDL_INIT:
cdrom->delay = CD_DELAY_INIT_FR;
break;
default:
cdrom->delay = CD_DELAY_FR;
break;
}
if (cdrom->state == CD_STATE_READ)
cdrom->busy = 1;
printf("cdrom: %-16s (%02x) params: ", cdrom_cmd_names[data], data);
if (queue_is_empty(cdrom->parameters)) {
print("(none)\n");
return;
}
for (int i = 0; i < cdrom->parameters->write_index; i++)
printf("%02x ", cdrom->parameters->buf[i]);
putchar('\n');
return;
}
void cdrom_write_null(psx_cdrom_t* cdrom, uint8_t data) {
USED(cdrom);
USED(data);
/* Ignore writes */
}
void cdrom_write_parm(psx_cdrom_t* cdrom, uint8_t data) {
queue_push(cdrom->parameters, data);
}
void cdrom_write_ier(psx_cdrom_t* cdrom, uint8_t data) {
cdrom->ier = data;
}
void cdrom_write_ifr(psx_cdrom_t* cdrom, uint8_t data) {
if (data & 0x40)
queue_clear(cdrom->parameters);
// uint8_t prev_ifr = cdrom->ifr & 0x1f;
cdrom->ifr &= ~(data & 0x1f);
// If an INT is acknowledged, then the response
// FIFO is cleared
// if (((cdrom->ifr & 0x1f) == 0) && (prev_ifr != 0))
// queue_clear(cdrom->response);
}
void cdrom_write_req(psx_cdrom_t* cdrom, uint8_t data) {
cdrom->data_req = data & 0x80;
}
void cdrom_write_vol0(psx_cdrom_t* cdrom, uint8_t data) {
cdrom->vol_pending[0] = data;
}
void cdrom_write_vol1(psx_cdrom_t* cdrom, uint8_t data) {
cdrom->vol_pending[1] = data;
}
void cdrom_write_vol2(psx_cdrom_t* cdrom, uint8_t data) {
cdrom->vol_pending[2] = data;
}
void cdrom_write_vol3(psx_cdrom_t* cdrom, uint8_t data) {
cdrom->vol_pending[3] = data;
}
void cdrom_write_vapp(psx_cdrom_t* cdrom, uint8_t data) {
cdrom->xa_mute = data & 1;
cdrom->vol[0] = cdrom->vol_pending[0];
cdrom->vol[1] = cdrom->vol_pending[1];
cdrom->vol[2] = cdrom->vol_pending[2];
cdrom->vol[3] = cdrom->vol_pending[3];
}
uint32_t psx_cdrom_read32(psx_cdrom_t* cdrom, uint32_t addr) {
uint32_t v0 = psx_cdrom_read8(cdrom, addr);
uint32_t v1 = psx_cdrom_read8(cdrom, addr + 1);
uint32_t v2 = psx_cdrom_read8(cdrom, addr + 2);
uint32_t v3 = psx_cdrom_read8(cdrom, addr + 3);
return v0 | (v1 << 8) | (v2 << 16) | (v3 << 24);
}
uint32_t psx_cdrom_read16(psx_cdrom_t* cdrom, uint32_t addr) {
// The CDROM controller is connected to the SUB-BUS which is a 16-bit
// bus, but the output from the controller itself is 8-bit. I think
// 16-bit accesses are handled as a pair of 8-bit accesses
return psx_cdrom_read8(cdrom, addr) << 8 | psx_cdrom_read8(cdrom, addr+1);
}
void psx_cdrom_write32(psx_cdrom_t* cdrom, uint32_t addr, uint32_t value) {
psx_cdrom_write8(cdrom, addr + 0, value & 0xff);
psx_cdrom_write8(cdrom, addr + 1, (value >> 8) & 0xff);
psx_cdrom_write8(cdrom, addr + 2, (value >> 16) & 0xff);
psx_cdrom_write8(cdrom, addr + 3, (value >> 24) & 0xff);
}
void psx_cdrom_write16(psx_cdrom_t* cdrom, uint32_t addr, uint32_t value) {
psx_cdrom_write8(cdrom, addr + 0, value & 0xff);
psx_cdrom_write8(cdrom, addr + 1, (value >> 8) & 0xff);
}