#endif
// DMAC handling
-static struct {
- unsigned int sar0, dar0, tcr0; // src addr, dst addr, transfer count
- unsigned int chcr0; // chan ctl
- unsigned int sar1, dar1, tcr1; // same for chan 1
- unsigned int chcr1;
- int pad[4];
+struct dma_chan {
+ unsigned int sar, dar; // src, dst addr
+ unsigned int tcr; // transfer count
+ unsigned int chcr; // chan ctl
+ // -- dm dm sm sm ts ts ar am al ds dl tb ta ie te de
+ // ts - transfer size: 1, 2, 4, 16 bytes
+ // ar - auto request if 1, else dreq signal
+ // ie - irq enable
+ // te - transfer end
+ // de - dma enable
+ #define DMA_AR (1 << 9)
+ #define DMA_IE (1 << 2)
+ #define DMA_TE (1 << 1)
+ #define DMA_DE (1 << 0)
+};
+
+struct dmac {
+ struct dma_chan chan[2];
+ unsigned int vcrdma0;
+ unsigned int unknown0;
+ unsigned int vcrdma1;
+ unsigned int unknown1;
unsigned int dmaor;
-} * dmac0;
+ // -- pr ae nmif dme
+ // pr - priority: chan0 > chan1 or round-robin
+ // ae - address error
+ // nmif - nmi occurred
+ // dme - DMA master enable
+ #define DMA_DME (1 << 0)
+};
+
+static void dmac_te_irq(SH2 *sh2, struct dma_chan *chan)
+{
+ char *regs = (void *)Pico32xMem->sh2_peri_regs[sh2->is_slave];
+ struct dmac *dmac = (void *)(regs + 0x180);
+ int level = PREG8(regs, 0xe2) & 0x0f; // IPRA
+ int vector = (chan == &dmac->chan[0]) ?
+ dmac->vcrdma0 : dmac->vcrdma1;
+
+ elprintf(EL_32X, "dmac irq %d %d", level, vector);
+ sh2_internal_irq(sh2, level, vector & 0x7f);
+}
+
+static void dmac_transfer_complete(SH2 *sh2, struct dma_chan *chan)
+{
+ chan->chcr |= DMA_TE; // DMA has ended normally
+
+ p32x_sh2_poll_event(sh2, SH2_STATE_SLEEP, SekCyclesDoneT());
+ if (chan->chcr & DMA_IE)
+ dmac_te_irq(sh2, chan);
+}
+
+static void dmac_transfer_one(SH2 *sh2, struct dma_chan *chan)
+{
+ u32 size, d;
+
+ size = (chan->chcr >> 10) & 3;
+ switch (size) {
+ case 0:
+ d = p32x_sh2_read8(chan->sar, sh2);
+ p32x_sh2_write8(chan->dar, d, sh2);
+ case 1:
+ d = p32x_sh2_read16(chan->sar, sh2);
+ p32x_sh2_write16(chan->dar, d, sh2);
+ break;
+ case 2:
+ d = p32x_sh2_read32(chan->sar, sh2);
+ p32x_sh2_write32(chan->dar, d, sh2);
+ break;
+ case 3:
+ elprintf(EL_32X|EL_ANOMALY, "TODO: 16byte DMA");
+ chan->sar += 16; // always?
+ chan->tcr -= 4;
+ return;
+ }
+ chan->tcr--;
+
+ size = 1 << size;
+ if (chan->chcr & (1 << 15))
+ chan->dar -= size;
+ if (chan->chcr & (1 << 14))
+ chan->dar += size;
+ if (chan->chcr & (1 << 13))
+ chan->sar -= size;
+ if (chan->chcr & (1 << 12))
+ chan->sar += size;
+}
-static void dma_68k2sh2_do(void)
+static void dreq0_do(SH2 *sh2, struct dma_chan *chan)
{
unsigned short *dreqlen = &Pico32x.regs[0x10 / 2];
int i;
- if (dmac0->tcr0 != *dreqlen)
- elprintf(EL_32X|EL_ANOMALY, "tcr0 and dreq len differ: %d != %d", dmac0->tcr0, *dreqlen);
+ // debug/sanity checks
+ if (chan->tcr != *dreqlen)
+ elprintf(EL_32X|EL_ANOMALY, "dreq0: tcr0 and len differ: %d != %d",
+ chan->tcr, *dreqlen);
+ // note: DACK is not connected, single addr mode should not be used
+ if ((chan->chcr & 0x3f08) != 0x0400)
+ elprintf(EL_32X|EL_ANOMALY, "dreq0: bad control: %04x", chan->chcr);
+ if (chan->sar != 0x20004012)
+ elprintf(EL_32X|EL_ANOMALY, "dreq0: bad sar?: %08x\n", chan->sar);
// HACK: assume bus is busy and SH2 is halted
- msh2.state |= SH2_STATE_SLEEP;
-
- for (i = 0; i < Pico32x.dmac_ptr && dmac0->tcr0 > 0; i++) {
- elprintf(EL_32X, "dmaw [%08x] %04x, left %d", dmac0->dar0, Pico32x.dmac_fifo[i], *dreqlen);
- p32x_sh2_write16(dmac0->dar0, Pico32x.dmac_fifo[i], &msh2);
- dmac0->dar0 += 2;
- dmac0->tcr0--;
+ sh2->state |= SH2_STATE_SLEEP;
+
+ for (i = 0; i < Pico32x.dmac0_fifo_ptr && chan->tcr > 0; i++) {
+ elprintf(EL_32X, "dmaw [%08x] %04x, left %d",
+ chan->dar, Pico32x.dmac_fifo[i], *dreqlen);
+ p32x_sh2_write16(chan->dar, Pico32x.dmac_fifo[i], sh2);
+ chan->dar += 2;
+ chan->tcr--;
(*dreqlen)--;
}
- Pico32x.dmac_ptr = 0; // HACK
+ if (Pico32x.dmac0_fifo_ptr != i)
+ memmove(Pico32x.dmac_fifo, &Pico32x.dmac_fifo[i],
+ (Pico32x.dmac0_fifo_ptr - i) * 2);
+ Pico32x.dmac0_fifo_ptr -= i;
+
Pico32x.regs[6 / 2] &= ~P32XS_FULL;
if (*dreqlen == 0)
Pico32x.regs[6 / 2] &= ~P32XS_68S; // transfer complete
- if (dmac0->tcr0 == 0) {
- dmac0->chcr0 |= 2; // DMA has ended normally
- p32x_sh2_poll_event(&sh2s[0], SH2_STATE_SLEEP, SekCyclesDoneT());
+ if (chan->tcr == 0)
+ dmac_transfer_complete(sh2, chan);
+ else
+ sh2_end_run(sh2, 16);
+}
+
+static void dreq1_do(SH2 *sh2, struct dma_chan *chan)
+{
+ // debug/sanity checks
+ if ((chan->chcr & 0xc308) != 0x0000)
+ elprintf(EL_32X|EL_ANOMALY, "dreq1: bad control: %04x", chan->chcr);
+ if ((chan->dar & ~0xf) != 0x20004030)
+ elprintf(EL_32X|EL_ANOMALY, "dreq1: bad dar?: %08x\n", chan->dar);
+
+ dmac_transfer_one(sh2, chan);
+ if (chan->tcr == 0)
+ dmac_transfer_complete(sh2, chan);
+}
+
+static void dreq0_trigger(void)
+{
+ struct dmac *mdmac = (void *)&Pico32xMem->sh2_peri_regs[0][0x180 / 4];
+ struct dmac *sdmac = (void *)&Pico32xMem->sh2_peri_regs[1][0x180 / 4];
+
+ elprintf(EL_32X, "dreq0_trigger\n");
+ if ((mdmac->dmaor & DMA_DME) && (mdmac->chan[0].chcr & 3) == DMA_DE) {
+ dreq0_do(&msh2, &mdmac->chan[0]);
+ }
+ if ((sdmac->dmaor & DMA_DME) && (sdmac->chan[0].chcr & 3) == DMA_DE) {
+ dreq0_do(&ssh2, &sdmac->chan[0]);
}
}
+void p32x_dreq1_trigger(void)
+{
+ struct dmac *mdmac = (void *)&Pico32xMem->sh2_peri_regs[0][0x180 / 4];
+ struct dmac *sdmac = (void *)&Pico32xMem->sh2_peri_regs[1][0x180 / 4];
+ int hit = 0;
+
+ elprintf(EL_32X, "dreq1_trigger\n");
+ if ((mdmac->dmaor & DMA_DME) && (mdmac->chan[1].chcr & 3) == DMA_DE) {
+ dreq1_do(&msh2, &mdmac->chan[1]);
+ hit = 1;
+ }
+ if ((sdmac->dmaor & DMA_DME) && (sdmac->chan[1].chcr & 3) == DMA_DE) {
+ dreq1_do(&ssh2, &sdmac->chan[1]);
+ hit = 1;
+ }
+
+ if (!hit)
+ elprintf(EL_32X|EL_ANOMALY, "dreq1: nobody cared");
+}
+
+// DMA trigger by SH2 register write
+static void dmac_trigger(SH2 *sh2, struct dma_chan *chan)
+{
+ elprintf(EL_32X, "sh2 DMA %08x->%08x, cnt %d, chcr %04x @%06x",
+ chan->sar, chan->dar, chan->tcr, chan->chcr, sh2->pc);
+ chan->tcr &= 0xffffff;
+
+ if (chan->chcr & DMA_AR) {
+ // auto-request transfer
+ while ((int)chan->tcr > 0)
+ dmac_transfer_one(sh2, chan);
+ dmac_transfer_complete(sh2, chan);
+ return;
+ }
+
+ // DREQ0 is only sent after first 4 words are written.
+ // we do multiple of 4 words to avoid messing up alignment
+ if (chan->sar == 0x20004012) {
+ if (Pico32x.dmac0_fifo_ptr && (Pico32x.dmac0_fifo_ptr & 3) == 0) {
+ elprintf(EL_32X, "68k -> sh2 DMA");
+ dreq0_trigger();
+ }
+ return;
+ }
+
+ elprintf(EL_32X|EL_ANOMALY, "unhandled DMA: "
+ "%08x->%08x, cnt %d, chcr %04x @%06x",
+ chan->sar, chan->dar, chan->tcr, chan->chcr, sh2->pc);
+}
+
// ------------------------------------------------------------------
// 68k regs
}
if ((a & 0x30) == 0x30)
- return p32x_pwm_read16(a, SekCyclesDoneT() * 3);
+ return p32x_pwm_read16(a, SekCyclesDoneT());
out:
return Pico32x.regs[a / 2];
elprintf(EL_32X|EL_ANOMALY, "DREQ FIFO w16 without 68S?");
return;
}
- if (Pico32x.dmac_ptr < DMAC_FIFO_LEN) {
- Pico32x.dmac_fifo[Pico32x.dmac_ptr++] = d;
- if ((Pico32x.dmac_ptr & 3) == 0 && (dmac0->chcr0 & 3) == 1 && (dmac0->dmaor & 1))
- dma_68k2sh2_do();
- if (Pico32x.dmac_ptr == DMAC_FIFO_LEN)
+ if (Pico32x.dmac0_fifo_ptr < DMAC_FIFO_LEN) {
+ Pico32x.dmac_fifo[Pico32x.dmac0_fifo_ptr++] = d;
+ if ((Pico32x.dmac0_fifo_ptr & 3) == 0)
+ dreq0_trigger();
+ if (Pico32x.dmac0_fifo_ptr == DMAC_FIFO_LEN)
r[6 / 2] |= P32XS_FULL;
}
break;
}
// PWM
else if ((a & 0x30) == 0x30) {
- p32x_pwm_write16(a, d, SekCyclesDoneT() * 3);
+ p32x_pwm_write16(a, d, SekCyclesDoneT());
return;
}
return r[a / 2];
}
if ((a & 0x30) == 0x30) {
- return p32x_pwm_read16(a, sh2_cycles_done_t(&sh2s[cpuid]));
+ return p32x_pwm_read16(a, sh2_cycles_done_m68k(&sh2s[cpuid]));
}
return 0;
}
// PWM
else if ((a & 0x30) == 0x30) {
- p32x_pwm_write16(a, d, sh2_cycles_done_t(&sh2s[cpuid]));
+ p32x_pwm_write16(a, d, sh2_cycles_done_m68k(&sh2s[cpuid]));
return;
}
case 0x1a: Pico32x.sh2irqi[cpuid] &= ~P32XI_CMD; goto irls;
case 0x1c:
Pico32x.sh2irqs &= ~P32XI_PWM;
- if (!(Pico32x.emu_flags & P32XF_PWM_PEND))
- p32x_pwm_schedule_sh2(&sh2s[cpuid]);
+ p32x_pwm_schedule_sh2(&sh2s[cpuid]);
goto irls;
}
break;
}
- if ((a == 0x1b0 || a == 0x18c) && (dmac0->chcr0 & 3) == 1 && (dmac0->dmaor & 1)) {
- elprintf(EL_32X, "sh2 DMA %08x -> %08x, cnt %d, chcr %04x @%06x",
- dmac0->sar0, dmac0->dar0, dmac0->tcr0, dmac0->chcr0, sh2_pc(id));
- dmac0->tcr0 &= 0xffffff;
-
- // HACK: assume 68k starts writing soon and end the timeslice
- sh2_end_run(&sh2s[id], 16);
+ // perhaps starting a DMA?
+ if (a == 0x1b0 || a == 0x18c || a == 0x19c) {
+ struct dmac *dmac = (void *)&Pico32xMem->sh2_peri_regs[id][0x180 / 4];
+ if (!(dmac->dmaor & DMA_DME))
+ return;
- // DREQ is only sent after first 4 words are written.
- // we do multiple of 4 words to avoid messing up alignment
- if (dmac0->sar0 == 0x20004012 && Pico32x.dmac_ptr && (Pico32x.dmac_ptr & 3) == 0) {
- elprintf(EL_32X, "68k -> sh2 DMA");
- dma_68k2sh2_do();
- }
+ if ((dmac->chan[0].chcr & (DMA_TE|DMA_DE)) == DMA_DE)
+ dmac_trigger(&sh2s[id], &dmac->chan[0]);
+ if ((dmac->chan[1].chcr & (DMA_TE|DMA_DE)) == DMA_DE)
+ dmac_trigger(&sh2s[id], &dmac->chan[1]);
}
}
return;
}
- dmac0 = (void *)&Pico32xMem->sh2_peri_regs[0][0x180 / 4];
-
get_bios();
// cartridge area becomes unmapped
static int pwm_cycles;
static int pwm_mult;
static int pwm_ptr;
+static int pwm_irq_reload;
static int timer_cycles[2];
static int timer_tick_cycles[2];
// timers. This includes PWM timer in 32x and internal SH2 timers
void p32x_timers_recalc(void)
{
+ int control = Pico32x.regs[0x30 / 2];
int cycles = Pico32x.regs[0x32 / 2];
int tmp, i;
pwm_cycles = cycles;
pwm_mult = 0x10000 / cycles;
+ pwm_irq_reload = (control & 0x0f00) >> 8;
+ pwm_irq_reload = ((pwm_irq_reload - 1) & 0x0f) + 1;
+
+ if (Pico32x.pwm_irq_cnt == 0)
+ Pico32x.pwm_irq_cnt = pwm_irq_reload;
+
// SH2 timer step
for (i = 0; i < 2; i++) {
tmp = PREG8(Pico32xMem->sh2_peri_regs[i], 0x80) & 7;
}
}
-#define consume_fifo(cycles) { \
- int cycles_diff = (cycles) - Pico32x.pwm_cycle_p; \
+static void do_pwm_irq(unsigned int m68k_cycles)
+{
+ Pico32x.sh2irqs |= P32XI_PWM;
+ p32x_update_irls(NULL);
+
+ if (Pico32x.regs[0x30 / 2] & P32XP_RTP) {
+ p32x_event_schedule(m68k_cycles, P32X_EVENT_PWM, pwm_cycles / 3 + 1);
+ // note: might recurse
+ p32x_dreq1_trigger();
+ }
+}
+
+#define consume_fifo(m68k_cycles) { \
+ int cycles_diff = ((m68k_cycles) * 3) - Pico32x.pwm_cycle_p; \
if (cycles_diff >= pwm_cycles) \
- consume_fifo_do((cycles), cycles_diff); \
+ consume_fifo_do(m68k_cycles, cycles_diff); \
}
-static void consume_fifo_do(unsigned int cycles, int cycles_diff)
+static void consume_fifo_do(unsigned int m68k_cycles, int sh2_cycles_diff)
{
+ int do_irq = 0;
+
if (pwm_cycles == 0)
return;
elprintf(EL_PWM, "pwm: %u: consume %d/%d, %d,%d ptr %d",
- cycles, cycles_diff, cycles_diff / pwm_cycles,
+ m68k_cycles, sh2_cycles_diff, sh2_cycles_diff / pwm_cycles,
Pico32x.pwm_p[0], Pico32x.pwm_p[1], pwm_ptr);
- if (cycles_diff > pwm_cycles * 9) {
+ if (sh2_cycles_diff >= pwm_cycles * 17) {
// silence/skip
- Pico32x.pwm_cycle_p = cycles;
+ Pico32x.pwm_cycle_p = m68k_cycles * 3;
Pico32x.pwm_p[0] = Pico32x.pwm_p[1] = 0;
return;
}
- for (; cycles_diff >= pwm_cycles; cycles_diff -= pwm_cycles) {
+ while (sh2_cycles_diff >= pwm_cycles) {
struct Pico32xMem *mem = Pico32xMem;
short *fifo_l = mem->pwm_fifo[0];
short *fifo_r = mem->pwm_fifo[1];
mem->pwm[pwm_ptr * 2 ] = fifo_l[0];
mem->pwm[pwm_ptr * 2 + 1] = fifo_r[0];
pwm_ptr = (pwm_ptr + 1) & (PWM_BUFF_LEN - 1);
+
+ sh2_cycles_diff -= pwm_cycles;
+
+ if (--Pico32x.pwm_irq_cnt == 0) {
+ Pico32x.pwm_irq_cnt = pwm_irq_reload;
+ // irq also does dreq1, so call it after cycle update
+ do_irq = 1;
+ break;
+ }
}
- Pico32x.pwm_cycle_p = cycles - cycles_diff;
+ Pico32x.pwm_cycle_p = m68k_cycles * 3 - sh2_cycles_diff;
+
+ if (do_irq)
+ do_pwm_irq(m68k_cycles);
}
void p32x_timers_do(unsigned int m68k_now, unsigned int m68k_slice)
unsigned int cycles = m68k_slice * 3;
int cnt, i;
- consume_fifo(m68k_now * 3);
+ //consume_fifo(m68k_now);
// WDT timers
for (i = 0; i < 2; i++) {
}
}
-static int p32x_pwm_schedule_(void)
+static int p32x_pwm_schedule_(unsigned int m68k_now)
{
- int tm;
+ unsigned int sh2_now = m68k_now * 3;
+ int cycles_diff_sh2;
+
+ if (pwm_cycles == 0)
+ return 0;
+
+ cycles_diff_sh2 = sh2_now - Pico32x.pwm_cycle_p;
+ if (cycles_diff_sh2 >= pwm_cycles)
+ consume_fifo_do(m68k_now, cycles_diff_sh2);
- if (Pico32x.emu_flags & P32XF_PWM_PEND)
- return 0; // already scheduled
if (Pico32x.sh2irqs & P32XI_PWM)
return 0; // previous not acked
if (!((Pico32x.sh2irq_mask[0] | Pico32x.sh2irq_mask[1]) & 1))
return 0; // masked by everyone
- Pico32x.emu_flags |= P32XF_PWM_PEND;
- tm = (Pico32x.regs[0x30 / 2] & 0x0f00) >> 8;
- tm = ((tm - 1) & 0x0f) + 1;
- return pwm_cycles * tm / 3;
+ cycles_diff_sh2 = sh2_now - Pico32x.pwm_cycle_p;
+ return (Pico32x.pwm_irq_cnt * pwm_cycles
+ - cycles_diff_sh2) / 3 + 1;
}
-void p32x_pwm_schedule(unsigned int now)
+void p32x_pwm_schedule(unsigned int m68k_now)
{
- int after = p32x_pwm_schedule_();
+ int after = p32x_pwm_schedule_(m68k_now);
if (after != 0)
- p32x_event_schedule(now, P32X_EVENT_PWM, after);
+ p32x_event_schedule(m68k_now, P32X_EVENT_PWM, after);
}
void p32x_pwm_schedule_sh2(SH2 *sh2)
{
- int after = p32x_pwm_schedule_();
+ int after = p32x_pwm_schedule_(sh2_cycles_done_m68k(sh2));
if (after != 0)
p32x_event_schedule_sh2(sh2, P32X_EVENT_PWM, after);
}
-unsigned int p32x_pwm_read16(unsigned int a, unsigned int cycles)
+void p32x_pwm_irq_event(unsigned int m68k_now)
+{
+ p32x_pwm_schedule(m68k_now);
+}
+
+unsigned int p32x_pwm_read16(unsigned int a, unsigned int m68k_cycles)
{
unsigned int d = 0;
- consume_fifo(cycles);
+ consume_fifo(m68k_cycles);
a &= 0x0e;
switch (a) {
break;
}
- elprintf(EL_PWM, "pwm: read %02x %04x (p %d %d), c %u",
- a, d, Pico32x.pwm_p[0], Pico32x.pwm_p[1], cycles);
+ elprintf(EL_PWM, "pwm: %u: r16 %02x %04x (p %d %d)",
+ m68k_cycles, a, d, Pico32x.pwm_p[0], Pico32x.pwm_p[1]);
return d;
}
-void p32x_pwm_write16(unsigned int a, unsigned int d, unsigned int cycles)
+void p32x_pwm_write16(unsigned int a, unsigned int d,
+ unsigned int m68k_cycles)
{
- consume_fifo(cycles);
+ elprintf(EL_PWM, "pwm: %u: w16 %02x %04x (p %d %d)",
+ m68k_cycles, a & 0x0e, d, Pico32x.pwm_p[0], Pico32x.pwm_p[1]);
+
+ consume_fifo(m68k_cycles);
a &= 0x0e;
if (a == 0) { // control
// supposedly we should stop FIFO when xMd is 0,
// but mars test disagrees
Pico32x.regs[0x30 / 2] = d;
+ p32x_timers_recalc();
+ Pico32x.pwm_irq_cnt = pwm_irq_reload; // ?
}
else if (a == 2) { // cycle
Pico32x.regs[0x32 / 2] = d & 0x0fff;
p32x_timers_recalc();
- Pico32x.pwm_irq_sample_cnt = 0; // resets?
}
else if (a <= 8) {
d = (d - 1) & 0x0fff;
pwm_ptr = 0;
}
+void p32x_pwm_state_loaded(void)
+{
+ int cycles_diff_sh2;
+
+ p32x_timers_recalc();
+
+ // for old savestates
+ cycles_diff_sh2 = SekCycleCntT * 3 - Pico32x.pwm_cycle_p;
+ if (cycles_diff_sh2 >= pwm_cycles || cycles_diff_sh2 < 0) {
+ Pico32x.pwm_irq_cnt = pwm_irq_reload;
+ Pico32x.pwm_cycle_p = SekCycleCntT * 3;
+ p32x_pwm_schedule(SekCycleCntT);
+ }
+}
+
// vim:shiftwidth=2:ts=2:expandtab