#include #include #include #include "timer.h" #include "../log.h" #define T0_COUNTER timer->timer[0].counter #define T0_SYNC_EN timer->timer[0].sync_enable #define T0_SYNC_MODE timer->timer[0].sync_mode #define T0_RESET_TGT timer->timer[0].reset_target #define T0_IRQ_TGT timer->timer[0].irq_target #define T0_IRQ_MAX timer->timer[0].irq_max #define T0_IRQ_REPEAT timer->timer[0].irq_repeat #define T0_IRQ_TOGGLE timer->timer[0].irq_toggle #define T0_CLKSRC timer->timer[0].clk_source #define T0_IRQ timer->timer[0].irq #define T0_TGT_REACHED timer->timer[0].target_reached #define T0_MAX_REACHED timer->timer[0].max_reached #define T0_IRQ_FIRED timer->timer[0].irq_fired #define T0_PAUSED timer->timer[0].paused #define T0_BLANK_ONCE timer->timer[0].blank_once #define T1_COUNTER timer->timer[1].counter #define T1_SYNC_EN timer->timer[1].sync_enable #define T1_SYNC_MODE timer->timer[1].sync_mode #define T1_RESET_TGT timer->timer[1].reset_target #define T1_IRQ_TGT timer->timer[1].irq_target #define T1_IRQ_MAX timer->timer[1].irq_max #define T1_IRQ_REPEAT timer->timer[1].irq_repeat #define T1_IRQ_TOGGLE timer->timer[1].irq_toggle #define T1_CLKSRC timer->timer[1].clk_source #define T1_IRQ timer->timer[1].irq #define T1_TGT_REACHED timer->timer[1].target_reached #define T1_MAX_REACHED timer->timer[1].max_reached #define T1_IRQ_FIRED timer->timer[1].irq_fired #define T1_PAUSED timer->timer[1].paused #define T1_BLANK_ONCE timer->timer[1].blank_once #define T2_COUNTER timer->timer[2].counter #define T2_SYNC_EN timer->timer[2].sync_enable #define T2_SYNC_MODE timer->timer[2].sync_mode #define T2_RESET_TGT timer->timer[2].reset_target #define T2_IRQ_TGT timer->timer[2].irq_target #define T2_IRQ_MAX timer->timer[2].irq_max #define T2_IRQ_REPEAT timer->timer[2].irq_repeat #define T2_IRQ_TOGGLE timer->timer[2].irq_toggle #define T2_CLKSRC timer->timer[2].clk_source #define T2_IRQ timer->timer[2].irq #define T2_TGT_REACHED timer->timer[2].target_reached #define T2_MAX_REACHED timer->timer[2].max_reached #define T2_IRQ_FIRED timer->timer[2].irq_fired #define T2_PAUSED timer->timer[2].paused #define T2_BLANK_ONCE timer->timer[2].blank_once #define T2_DIV_COUNTER timer->timer[2].div_counter uint16_t timer_get_mode(psx_timer_t* timer, int index) { uint16_t value = (timer->timer[index].sync_enable << 0) | (timer->timer[index].sync_mode << 1) | (timer->timer[index].reset_target << 3) | (timer->timer[index].irq_target << 4) | (timer->timer[index].irq_max << 5) | (timer->timer[index].irq_repeat << 6) | (timer->timer[index].irq_toggle << 7) | (timer->timer[index].clk_source << 8) | (timer->timer[index].irq << 10) | (timer->timer[index].target_reached << 11) | (timer->timer[index].max_reached << 12); timer->timer[index].target_reached = 0; timer->timer[index].max_reached = 0; return value; } void timer_set_mode(psx_timer_t* timer, int index, uint16_t value) { timer->timer[index].sync_enable = (value >> 0) & 1; timer->timer[index].sync_mode = (value >> 1) & 3; timer->timer[index].reset_target = (value >> 3) & 1; timer->timer[index].irq_target = (value >> 4) & 1; timer->timer[index].irq_max = (value >> 5) & 1; timer->timer[index].irq_repeat = (value >> 6) & 1; timer->timer[index].irq_toggle = (value >> 7) & 1; timer->timer[index].clk_source = (value >> 8) & 3; timer->timer[index].target_reached = 0; timer->timer[index].max_reached = 0; // IRQ and counter are set to 0 on mode writes timer->timer[index].irq = 1; timer->timer[index].irq_fired = 0; timer->timer[index].counter = 0; timer->timer[index].div_counter = 0; timer->timer[index].blank_once = 0; timer->timer[index].paused = 0; // printf( // "timer_set_mode %u %04x\n" // "sync_enable %u\n" // "sync_mode %u\n" // "reset_target %u\n" // "irq_target %u\n" // "irq_max %u\n" // "irq_repeat %u\n" // "irq_toggle %u\n" // "clk_source %u\n" // "target_reached %u\n" // "max_reached %u\n" // "target %04x\n", // index, value, // timer->timer[index].sync_enable, // timer->timer[index].sync_mode, // timer->timer[index].reset_target, // timer->timer[index].irq_target, // timer->timer[index].irq_max, // timer->timer[index].irq_repeat, // timer->timer[index].irq_toggle, // timer->timer[index].clk_source, // timer->timer[index].target_reached, // timer->timer[index].max_reached, // timer->timer[index].target // ); switch (index) { case 0: { if ((T0_SYNC_MODE == 1) || (T0_SYNC_MODE == 2) || !T0_SYNC_EN) return; T0_PAUSED = timer->hblank | (T0_SYNC_MODE == 3); } break; case 1: { if ((T1_SYNC_MODE == 1) || (T1_SYNC_MODE == 2) || !T1_SYNC_EN) return; T1_PAUSED = timer->vblank | (T1_SYNC_MODE == 3); } break; case 2: { if (!T2_SYNC_EN) return; T2_PAUSED = (T2_SYNC_MODE == 0) || (T2_SYNC_MODE == 3); } break; } } const char* g_psx_timer_reg_names[] = { "counter", 0, 0, 0, "mode", 0, 0, 0, "target", 0, 0, 0 }; psx_timer_t* psx_timer_create(void) { return (psx_timer_t*)malloc(sizeof(psx_timer_t)); } void psx_timer_init(psx_timer_t* timer, psx_ic_t* ic, psx_gpu_t* gpu) { memset(timer, 0, sizeof(psx_timer_t)); timer->io_base = PSX_TIMER_BEGIN; timer->io_size = PSX_TIMER_SIZE; timer->ic = ic; timer->gpu = gpu; } uint32_t psx_timer_read32(psx_timer_t* timer, uint32_t offset) { int index = offset >> 4; int reg = offset & 0xf; switch (reg) { case 0: return timer->timer[index].counter; case 4: return timer_get_mode(timer, index); case 8: return timer->timer[index].target; } printf("Unhandled 32-bit TIMER read at offset %08x\n", offset); return 0x0; } uint16_t psx_timer_read16(psx_timer_t* timer, uint32_t offset) { int index = offset >> 4; int reg = offset & 0xf; switch (reg) { case 0: return timer->timer[index].counter; case 4: return timer_get_mode(timer, index); case 8: return timer->timer[index].target; } printf("Unhandled 16-bit TIMER read at offset %08x\n", offset); return 0x0; } uint8_t psx_timer_read8(psx_timer_t* timer, uint32_t offset) { printf("Unhandled 8-bit TIMER read at offset %08x\n", offset); return 0x0; } void timer_handle_irq(psx_timer_t* timer, int i); void psx_timer_write32(psx_timer_t* timer, uint32_t offset, uint32_t value) { int index = offset >> 4; int reg = offset & 0xf; switch (reg) { case 0: timer->timer[index].counter = value & 0xffff; break; case 4: timer_set_mode(timer, index, value); break; case 8: timer->timer[index].target = value & 0xffff; break; } timer_handle_irq(timer, index); } void psx_timer_write16(psx_timer_t* timer, uint32_t offset, uint16_t value) { int index = offset >> 4; int reg = offset & 0xf; switch (reg) { case 0: timer->timer[index].counter = value & 0xffff; break; case 4: timer_set_mode(timer, index, value); break; case 8: timer->timer[index].target = value & 0xffff; break; } timer_handle_irq(timer, index); } void psx_timer_write8(psx_timer_t* timer, uint32_t offset, uint8_t value) { printf("Unhandled 8-bit TIMER write at offset %08x (%02x)\n", offset, value); } void timer_handle_irq(psx_timer_t* timer, int i) { int irq = 0; int target_reached = timer->timer[i].counter > timer->timer[i].target; int max_reached = timer->timer[i].counter > 65535.0f; if (target_reached) { timer->timer[i].target_reached = 1; // if ((i == 1) && (T1_CLKSRC == 1)) // printf("target %04x (%f) reached\n", timer->timer[i].target, timer->timer[i].counter); if (timer->timer[i].reset_target) timer->timer[i].counter = 0; if (timer->timer[i].irq_target) irq = 1; } if (max_reached) { timer->timer[i].counter = 0; timer->timer[i].max_reached = 1; if (timer->timer[i].irq_max) irq = 1; } if (!irq) return; if (!timer->timer[i].irq_toggle) { timer->timer[i].irq = 0; } else { timer->timer[i].irq ^= 1; } int trigger = !timer->timer[i].irq; if (!timer->timer[i].irq_repeat) { if (trigger && !timer->timer[i].irq_fired) { timer->timer[i].irq_fired = 1; } else { return; } } timer->timer[i].irq = 1; if (trigger) { psx_ic_irq(timer->ic, 16 << i); } } float timer_get_dotclock_div(psx_timer_t* timer) { static const float dmode_dotclk_div_table[] = { 10.0f, 8.0f, 5.0f, 4.0f }; if (timer->gpu->display_mode & 0x40) { return 11.0f / 7.0f / 7.0f; } else { return 11.0f / 7.0f / dmode_dotclk_div_table[timer->gpu->display_mode & 0x3]; } } void timer_update_timer0(psx_timer_t* timer, int cyc) { if (T0_PAUSED) return; if (T0_CLKSRC & 1) { // Dotclock test T0_COUNTER += (float)cyc * timer_get_dotclock_div(timer); } else { T0_COUNTER += (float)cyc; } timer_handle_irq(timer, 0); } void timer_update_timer1(psx_timer_t* timer, int cyc) { if (T1_PAUSED) return; if (T1_CLKSRC & 1) { // Counter is incremented in our hblank callback } else { T1_COUNTER += (float)cyc; } timer_handle_irq(timer, 1); } void timer_update_timer2(psx_timer_t* timer, int cyc) { if (T2_PAUSED) return; if (T2_CLKSRC <= 1) { T2_COUNTER += (float)cyc; } else { T2_COUNTER += ((float)cyc) / 8.0f; } timer_handle_irq(timer, 2); } void psx_timer_update(psx_timer_t* timer, int cyc) { timer->prev_hblank = timer->hblank; timer->prev_vblank = timer->vblank; timer_update_timer0(timer, 2); timer_update_timer1(timer, 2); timer_update_timer2(timer, 2); } void psxe_gpu_hblank_event_cb(psx_gpu_t* gpu) { psx_timer_t* timer = gpu->udata[1]; timer->hblank = 1; if ((T1_CLKSRC & 1) && !T1_PAUSED) { ++T1_COUNTER; timer_handle_irq(timer, 1); } if (!T0_SYNC_EN) return; switch (T0_SYNC_MODE) { case 0: T0_PAUSED = 1; break; case 1: T0_COUNTER = 0; break; case 2: T0_COUNTER = 0; T0_PAUSED = 0; break; case 3: { if (!T0_BLANK_ONCE) { T0_BLANK_ONCE = 1; T0_SYNC_EN = 0; T0_PAUSED = 0; } } break; } } void psxe_gpu_hblank_end_event_cb(psx_gpu_t* gpu) { psx_timer_t* timer = gpu->udata[1]; timer->hblank = 0; if (!T0_SYNC_EN) return; switch (T0_SYNC_MODE) { case 0: T0_PAUSED = 0; break; case 2: T0_PAUSED = 1; break; } } void psxe_gpu_vblank_timer_event_cb(psx_gpu_t* gpu) { psx_timer_t* timer = gpu->udata[1]; timer->vblank = 1; if (!T1_SYNC_EN) return; switch (T1_SYNC_MODE) { case 0: T1_PAUSED = 1; break; case 1: T1_COUNTER = 0; break; case 2: T1_COUNTER = 0; T1_PAUSED = 0; break; case 3: { if (!T1_BLANK_ONCE) { T1_BLANK_ONCE = 1; T1_SYNC_EN = 0; T1_PAUSED = 0; } } break; } } void psxe_gpu_vblank_end_event_cb(psx_gpu_t* gpu) { psx_timer_t* timer = gpu->udata[1]; timer->vblank = 0; if (!T1_SYNC_EN) return; switch (T1_SYNC_MODE) { case 0: T1_PAUSED = 0; break; case 2: T1_PAUSED = 1; break; } } void psx_timer_destroy(psx_timer_t* timer) { free(timer); }