372 lines
11 KiB
C
372 lines
11 KiB
C
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ioptimer - IOP timers
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef EMU_COMPILE
|
|
#error "Hi I forgot to set EMU_COMPILE"
|
|
#endif
|
|
|
|
#include "ioptimer.h"
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Static information
|
|
//
|
|
sint32 EMU_CALL ioptimer_init(void) { return 0; }
|
|
|
|
#define COUNTERS (6)
|
|
|
|
#define IOP_INT_VBLANK (1<<0)
|
|
#define IOP_INT_RTC0 (1<<4)
|
|
#define IOP_INT_RTC1 (1<<5)
|
|
#define IOP_INT_RTC2 (1<<6)
|
|
#define IOP_INT_RTC3 (1<<14)
|
|
#define IOP_INT_RTC4 (1<<15)
|
|
#define IOP_INT_RTC5 (1<<16)
|
|
|
|
static const uint32 intrflag[COUNTERS] = {
|
|
IOP_INT_RTC0, IOP_INT_RTC1, IOP_INT_RTC2,
|
|
IOP_INT_RTC3, IOP_INT_RTC4, IOP_INT_RTC5
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// State information
|
|
//
|
|
#define IOPTIMERSTATE ((struct IOPTIMER_STATE*)(state))
|
|
|
|
struct IOPTIMER_COUNTER {
|
|
//
|
|
// quick values used in advance loop, etc.
|
|
//
|
|
uint64 counter;
|
|
uint32 delta;
|
|
uint64 target;
|
|
uint8 target_is_overflow;
|
|
//
|
|
// other values
|
|
//
|
|
uint16 mode;
|
|
uint16 status;
|
|
uint64 compare;
|
|
};
|
|
|
|
struct IOPTIMER_STATE {
|
|
struct IOPTIMER_COUNTER counter[COUNTERS];
|
|
uint8 gate;
|
|
uint64 field_counter;
|
|
uint64 field_vblank;
|
|
uint64 field_total;
|
|
uint32 hz_sysclock;
|
|
uint32 hz_hline;
|
|
uint32 hz_pixel;
|
|
};
|
|
|
|
uint32 EMU_CALL ioptimer_get_state_size(void) {
|
|
return sizeof(struct IOPTIMER_STATE);
|
|
}
|
|
|
|
void EMU_CALL ioptimer_clear_state(void *state) {
|
|
memset(IOPTIMERSTATE, 0, sizeof(struct IOPTIMER_STATE));
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
void EMU_CALL ioptimer_set_rates(void *state, uint32 sysclock, uint32 dots, uint32 lines, uint32 lines_visible, uint32 refresh_rate) {
|
|
IOPTIMERSTATE->hz_sysclock = sysclock;
|
|
IOPTIMERSTATE->hz_hline = lines * refresh_rate;
|
|
IOPTIMERSTATE->hz_pixel = IOPTIMERSTATE->hz_hline * dots;
|
|
|
|
IOPTIMERSTATE->field_counter = 0;
|
|
IOPTIMERSTATE->field_vblank = ((uint64)(lines_visible)) * ((uint64)(sysclock));
|
|
IOPTIMERSTATE->field_total = ((uint64)(lines )) * ((uint64)(sysclock));
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static EMU_INLINE uint32 EMU_CALL cycles_until_gate(struct IOPTIMER_STATE *state) {
|
|
uint64 diff;
|
|
if(!(IOPTIMERSTATE->hz_hline)) return 0xFFFFFFFF;
|
|
if(state->field_counter < state->field_vblank) {
|
|
diff = state->field_vblank - state->field_counter;
|
|
} else {
|
|
diff = state->field_total - state->field_counter;
|
|
}
|
|
diff += (IOPTIMERSTATE->hz_hline-1);
|
|
diff /= ((uint64)(IOPTIMERSTATE->hz_hline));
|
|
if(diff > 0xFFFFFFFF) diff = 0xFFFFFFFF;
|
|
if(diff < 1) diff = 1;
|
|
return (uint32)diff;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
uint32 EMU_CALL ioptimer_cycles_until_interrupt(void *state) {
|
|
uint32 min = cycles_until_gate(IOPTIMERSTATE);
|
|
uint32 c;
|
|
//
|
|
// counters
|
|
//
|
|
for(c = 0; c < COUNTERS; c++) {
|
|
uint64 diff;
|
|
if(!(IOPTIMERSTATE->counter[c].delta)) continue;
|
|
if(IOPTIMERSTATE->counter[c].counter >= IOPTIMERSTATE->counter[c].target) {
|
|
diff = 0;
|
|
} else {
|
|
diff = IOPTIMERSTATE->counter[c].target - IOPTIMERSTATE->counter[c].counter;
|
|
diff += (IOPTIMERSTATE->counter[c].delta-1);
|
|
diff /= ((uint64)(IOPTIMERSTATE->counter[c].delta));
|
|
}
|
|
if(diff < ((uint64)(min))) min = (uint32)diff;
|
|
}
|
|
if(min < 1) min = 1;
|
|
return min;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static EMU_INLINE uint32 EMU_CALL counters_advance(struct IOPTIMER_STATE *state, uint32 cycles) {
|
|
uint32 intr = 0;
|
|
uint32 c;
|
|
for(c = 0; c < COUNTERS; c++) {
|
|
struct IOPTIMER_COUNTER *ctr = IOPTIMERSTATE->counter + c;;
|
|
if(!ctr->delta) continue;
|
|
ctr->counter += ((uint64)(cycles)) * ((uint64)(ctr->delta));
|
|
//
|
|
// timer loop handling
|
|
//
|
|
for(;;) {
|
|
//
|
|
// if we're below the given target, then good - quit.
|
|
//
|
|
if(ctr->counter < ctr->target) break;
|
|
//
|
|
// otherwise, we have a transition to make.
|
|
//
|
|
if(ctr->target_is_overflow) {
|
|
ctr->status |= 0x1000;
|
|
if(ctr->mode & 0x20) intr |= intrflag[c];
|
|
// counter always loops on overflow (duh!)
|
|
ctr->counter -= ctr->target;
|
|
// counter now becomes the compare target
|
|
ctr->target = ((uint64)(state->hz_sysclock)) * ((uint64)(ctr->compare));
|
|
ctr->target_is_overflow = 0;
|
|
} else {
|
|
ctr->status |= 0x800;
|
|
if(ctr->mode & 0x10) intr |= intrflag[c];
|
|
// counter only loops on target if the appropriate bit is set
|
|
if(ctr->mode & 8) {
|
|
// no change to target, just loop counter
|
|
ctr->counter -= ctr->target;
|
|
// no target loop - proceed to overflow
|
|
} else {
|
|
if(c < 3) {
|
|
ctr->target = ((uint64)(state->hz_sysclock)) << 16;
|
|
} else {
|
|
ctr->target = ((uint64)(state->hz_sysclock)) << 32;
|
|
}
|
|
ctr->target_is_overflow = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return intr;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void EMU_CALL counter_start(struct IOPTIMER_STATE *state, uint32 c) {
|
|
struct IOPTIMER_COUNTER *ctr = state->counter + c;
|
|
uint32 delta = state->hz_sysclock;
|
|
switch(c) {
|
|
case 0: if(ctr->mode & 0x100) { delta = state->hz_pixel; } break;
|
|
case 1: if(ctr->mode & 0x100) { delta = state->hz_hline; } break;
|
|
case 2: if(ctr->mode & 0x200) { delta /= 8; } break;
|
|
case 3: if(ctr->mode & 0x100) { delta = state->hz_hline; } break;
|
|
case 4: case 5:
|
|
switch((ctr->mode >> 13) & 3) {
|
|
case 0: delta /= 1; break;
|
|
case 1: delta /= 8; break;
|
|
case 2: delta /= 16; break;
|
|
case 3: delta /= 256; break;
|
|
}
|
|
break;
|
|
}
|
|
ctr->counter = 0;
|
|
ctr->delta = delta;
|
|
ctr->target = ((uint64)(ctr->compare)) * ((uint64)(state->hz_sysclock));
|
|
ctr->target_is_overflow = 0;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void EMU_CALL counter_stop(struct IOPTIMER_STATE *state, uint32 c) {
|
|
state->counter[c].delta = 0;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void EMU_CALL gate_transition(struct IOPTIMER_STATE *state) {
|
|
uint32 c;
|
|
for(c = 0; c < COUNTERS; c++) {
|
|
// must be both enabled and gate-enabled
|
|
if((state->counter[c].mode & 0x41) != 0x41) continue;
|
|
switch(state->counter[c].mode & 0x6) {
|
|
case 0x0: // TM_GATE_ON_Count
|
|
if(state->gate) { counter_start(state, c); }
|
|
else { counter_stop(state, c); }
|
|
break;
|
|
case 0x2: // TM_GATE_ON_ClearStart
|
|
if(state->gate) { counter_start(state, c); }
|
|
break;
|
|
case 0x4: // TM_GATE_ON_Clear_OFF_Start
|
|
if(state->gate) { counter_stop(state, c); }
|
|
else { counter_start(state, c); }
|
|
break;
|
|
case 0x6: // TM_GATE_ON_Start
|
|
if(state->gate) {
|
|
// one-time start: disable gate bit
|
|
state->counter[c].mode &= ~1;
|
|
counter_start(state, c);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static EMU_INLINE uint32 EMU_CALL gate_advance(struct IOPTIMER_STATE *state, uint32 cycles) {
|
|
uint32 intr = 0;
|
|
state->field_counter += ((uint64)(cycles)) * ((uint64)(state->hz_hline));
|
|
//
|
|
// gate overflow loop
|
|
//
|
|
for(;;) {
|
|
//
|
|
// if we're below the given target, then good - quit.
|
|
//
|
|
if(state->gate) {
|
|
if(state->field_counter < state->field_vblank) break;
|
|
//
|
|
// gate transition 1->0
|
|
//
|
|
state->gate = 0;
|
|
gate_transition(state);
|
|
intr |= IOP_INT_VBLANK;
|
|
} else {
|
|
if(state->field_counter < state->field_total) break;
|
|
//
|
|
// gate transition 0->1
|
|
//
|
|
state->gate = 1;
|
|
gate_transition(state);
|
|
state->field_counter -= state->field_total;
|
|
}
|
|
}
|
|
return intr;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
uint32 EMU_CALL ioptimer_advance(void *state, uint32 cycles) {
|
|
uint32 intr = 0;
|
|
uint32 cycles_left = cycles;
|
|
while(cycles_left) {
|
|
uint32 g = cycles_until_gate(IOPTIMERSTATE);
|
|
if(g > cycles_left) g = cycles_left;
|
|
intr |= counters_advance(IOPTIMERSTATE, g);
|
|
intr |= gate_advance(IOPTIMERSTATE, g);
|
|
cycles_left -= g;
|
|
}
|
|
return intr;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static uint32 EMU_CALL which_counter(uint32 a) {
|
|
switch(a & 0xFFF0) {
|
|
case 0x1100: return 0;
|
|
case 0x1110: return 1;
|
|
case 0x1120: return 2;
|
|
case 0x1480: return 3;
|
|
case 0x1490: return 4;
|
|
case 0x14A0: return 5;
|
|
}
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
uint32 EMU_CALL ioptimer_lw(void *state, uint32 a, uint32 mask) {
|
|
uint32 d = 0;
|
|
uint32 c = which_counter(a);
|
|
struct IOPTIMER_COUNTER *ctr;
|
|
if(c >= COUNTERS) return 0;
|
|
ctr = IOPTIMERSTATE->counter + c;
|
|
switch(a & 0xC) {
|
|
case 0x0:
|
|
if(ctr->delta) { d = (uint32)((ctr->counter) / ((uint64)(ctr->delta))); }
|
|
break;
|
|
case 0x4:
|
|
d = ctr->status;
|
|
ctr->status = 0;
|
|
break;
|
|
case 0x8:
|
|
d = (uint32)(ctr->compare);
|
|
break;
|
|
}
|
|
if(c < 3) d &= 0xFFFF;
|
|
return d & mask;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
void EMU_CALL ioptimer_sw(void *state, uint32 a, uint32 d, uint32 mask) {
|
|
uint32 c = which_counter(a);
|
|
struct IOPTIMER_COUNTER *ctr;
|
|
if(c >= COUNTERS) return;
|
|
ctr = IOPTIMERSTATE->counter + c;
|
|
d &= mask;
|
|
if(c < 3) d &= 0xFFFF;
|
|
switch(a & 0xC) {
|
|
case 0x4:
|
|
ctr->delta = 0;
|
|
ctr->mode = d;
|
|
if(d & 0x40) {
|
|
if((d & 7) != 7) {
|
|
counter_start(state, c);
|
|
}
|
|
}
|
|
break;
|
|
case 0x8:
|
|
ctr->compare = d;
|
|
if(!ctr->compare) {
|
|
if(c < 3) {
|
|
ctr->compare = 0x10000;
|
|
} else {
|
|
ctr->compare = 0x100000000;
|
|
}
|
|
}
|
|
//
|
|
// if this timer was running, recompute the target
|
|
//
|
|
if(ctr->delta) {
|
|
ctr->target = ctr->compare * ((uint64)(IOPTIMERSTATE->hz_sysclock));
|
|
ctr->target_is_overflow = 0;
|
|
if(ctr->counter >= ctr->target) {
|
|
if(c < 3) {
|
|
ctr->target = ((uint64)(IOPTIMERSTATE->hz_sysclock)) << 16;
|
|
} else {
|
|
ctr->target = ((uint64)(IOPTIMERSTATE->hz_sysclock)) << 32;
|
|
}
|
|
ctr->target_is_overflow = 1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|