Cog/Frameworks/HighlyExperimental/HighlyExperimental/Core/ioptimer.c
2013-09-30 03:36:30 -07:00

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;
}
}
/////////////////////////////////////////////////////////////////////////////