32x: improve interrupt handling
[picodrive.git] / pico / 32x / pwm.c
1 /*
2  * PicoDrive
3  * (C) notaz, 2009,2010,2013
4  *
5  * This work is licensed under the terms of MAME license.
6  * See COPYING file in the top-level directory.
7  */
8 #include "../pico_int.h"
9
10 static int pwm_cycles;
11 static int pwm_mult;
12 static int pwm_ptr;
13 static int pwm_irq_reload;
14 static int pwm_doing_fifo;
15
16 void p32x_pwm_ctl_changed(void)
17 {
18   int control = Pico32x.regs[0x30 / 2];
19   int cycles = Pico32x.regs[0x32 / 2];
20
21   cycles = (cycles - 1) & 0x0fff;
22   pwm_cycles = cycles;
23   pwm_mult = 0x10000 / cycles;
24
25   pwm_irq_reload = (control & 0x0f00) >> 8;
26   pwm_irq_reload = ((pwm_irq_reload - 1) & 0x0f) + 1;
27
28   if (Pico32x.pwm_irq_cnt == 0)
29     Pico32x.pwm_irq_cnt = pwm_irq_reload;
30 }
31
32 static void do_pwm_irq(SH2 *sh2, unsigned int m68k_cycles)
33 {
34   p32x_trigger_irq(sh2, m68k_cycles, P32XI_PWM);
35
36   if (Pico32x.regs[0x30 / 2] & P32XP_RTP) {
37     p32x_event_schedule(m68k_cycles, P32X_EVENT_PWM, pwm_cycles / 3 + 1);
38     // note: might recurse
39     p32x_dreq1_trigger();
40   }
41 }
42
43 #define consume_fifo(sh2, m68k_cycles) { \
44   int cycles_diff = ((m68k_cycles) * 3) - Pico32x.pwm_cycle_p; \
45   if (cycles_diff >= pwm_cycles) \
46     consume_fifo_do(sh2, m68k_cycles, cycles_diff); \
47 }
48
49 static void consume_fifo_do(SH2 *sh2, unsigned int m68k_cycles,
50   int sh2_cycles_diff)
51 {
52   if (pwm_cycles == 0 || pwm_doing_fifo)
53     return;
54
55   elprintf(EL_PWM, "pwm: %u: consume %d/%d, %d,%d ptr %d",
56     m68k_cycles, sh2_cycles_diff, sh2_cycles_diff / pwm_cycles,
57     Pico32x.pwm_p[0], Pico32x.pwm_p[1], pwm_ptr);
58
59   // this is for recursion from dreq1 writes
60   pwm_doing_fifo = 1;
61
62   while (sh2_cycles_diff >= pwm_cycles) {
63     struct Pico32xMem *mem = Pico32xMem;
64     short *fifo_l = mem->pwm_fifo[0];
65     short *fifo_r = mem->pwm_fifo[1];
66
67     if (Pico32x.pwm_p[0] > 0) {
68       fifo_l[0] = fifo_l[1];
69       fifo_l[1] = fifo_l[2];
70       fifo_l[2] = fifo_l[3];
71       Pico32x.pwm_p[0]--;
72     }
73     if (Pico32x.pwm_p[1] > 0) {
74       fifo_r[0] = fifo_r[1];
75       fifo_r[1] = fifo_r[2];
76       fifo_r[2] = fifo_r[3];
77       Pico32x.pwm_p[1]--;
78     }
79
80     mem->pwm[pwm_ptr * 2    ] = fifo_l[0];
81     mem->pwm[pwm_ptr * 2 + 1] = fifo_r[0];
82     pwm_ptr = (pwm_ptr + 1) & (PWM_BUFF_LEN - 1);
83
84     sh2_cycles_diff -= pwm_cycles;
85
86     if (--Pico32x.pwm_irq_cnt == 0) {
87       Pico32x.pwm_irq_cnt = pwm_irq_reload;
88       do_pwm_irq(sh2, m68k_cycles);
89     }
90   }
91   Pico32x.pwm_cycle_p = m68k_cycles * 3 - sh2_cycles_diff;
92   pwm_doing_fifo = 0;
93 }
94
95 static int p32x_pwm_schedule_(SH2 *sh2, unsigned int m68k_now)
96 {
97   unsigned int sh2_now = m68k_now * 3;
98   int cycles_diff_sh2;
99
100   if (pwm_cycles == 0)
101     return 0;
102
103   cycles_diff_sh2 = sh2_now - Pico32x.pwm_cycle_p;
104   if (cycles_diff_sh2 >= pwm_cycles)
105     consume_fifo_do(sh2, m68k_now, cycles_diff_sh2);
106
107   if (!((Pico32x.sh2irq_mask[0] | Pico32x.sh2irq_mask[1]) & 1))
108     return 0; // masked by everyone
109
110   cycles_diff_sh2 = sh2_now - Pico32x.pwm_cycle_p;
111   return (Pico32x.pwm_irq_cnt * pwm_cycles
112            - cycles_diff_sh2) / 3 + 1;
113 }
114
115 void p32x_pwm_schedule(unsigned int m68k_now)
116 {
117   int after = p32x_pwm_schedule_(NULL, m68k_now);
118   if (after != 0)
119     p32x_event_schedule(m68k_now, P32X_EVENT_PWM, after);
120 }
121
122 void p32x_pwm_schedule_sh2(SH2 *sh2)
123 {
124   int after = p32x_pwm_schedule_(sh2, sh2_cycles_done_m68k(sh2));
125   if (after != 0)
126     p32x_event_schedule_sh2(sh2, P32X_EVENT_PWM, after);
127 }
128
129 void p32x_pwm_sync_to_sh2(SH2 *sh2)
130 {
131   int m68k_cycles = sh2_cycles_done_m68k(sh2);
132   consume_fifo(sh2, m68k_cycles);
133 }
134
135 void p32x_pwm_irq_event(unsigned int m68k_now)
136 {
137   p32x_pwm_schedule(m68k_now);
138 }
139
140 unsigned int p32x_pwm_read16(unsigned int a, SH2 *sh2,
141   unsigned int m68k_cycles)
142 {
143   unsigned int d = 0;
144
145   consume_fifo(sh2, m68k_cycles);
146
147   a &= 0x0e;
148   switch (a) {
149     case 0: // control
150     case 2: // cycle
151       d = Pico32x.regs[(0x30 + a) / 2];
152       break;
153
154     case 4: // L ch
155       if (Pico32x.pwm_p[0] == 3)
156         d |= P32XP_FULL;
157       else if (Pico32x.pwm_p[0] == 0)
158         d |= P32XP_EMPTY;
159       break;
160
161     case 6: // R ch
162     case 8: // MONO
163       if (Pico32x.pwm_p[1] == 3)
164         d |= P32XP_FULL;
165       else if (Pico32x.pwm_p[1] == 0)
166         d |= P32XP_EMPTY;
167       break;
168   }
169
170   elprintf(EL_PWM, "pwm: %u: r16 %02x %04x (p %d %d)",
171     m68k_cycles, a, d, Pico32x.pwm_p[0], Pico32x.pwm_p[1]);
172   return d;
173 }
174
175 void p32x_pwm_write16(unsigned int a, unsigned int d,
176   SH2 *sh2, unsigned int m68k_cycles)
177 {
178   elprintf(EL_PWM, "pwm: %u: w16 %02x %04x (p %d %d)",
179     m68k_cycles, a & 0x0e, d, Pico32x.pwm_p[0], Pico32x.pwm_p[1]);
180
181   consume_fifo(sh2, m68k_cycles);
182
183   a &= 0x0e;
184   if (a == 0) { // control
185     // supposedly we should stop FIFO when xMd is 0,
186     // but mars test disagrees
187     Pico32x.regs[0x30 / 2] = d;
188     p32x_pwm_ctl_changed();
189     Pico32x.pwm_irq_cnt = pwm_irq_reload; // ?
190   }
191   else if (a == 2) { // cycle
192     Pico32x.regs[0x32 / 2] = d & 0x0fff;
193     p32x_pwm_ctl_changed();
194   }
195   else if (a <= 8) {
196     d = (d - 1) & 0x0fff;
197     if (d > pwm_cycles)
198       d = pwm_cycles;
199     d = (d - pwm_cycles / 2) * pwm_mult;
200
201     if (a == 4 || a == 8) { // L ch or MONO
202       short *fifo = Pico32xMem->pwm_fifo[0];
203       if (Pico32x.pwm_p[0] < 3)
204         Pico32x.pwm_p[0]++;
205       else {
206         fifo[1] = fifo[2];
207         fifo[2] = fifo[3];
208       }
209       fifo[Pico32x.pwm_p[0]] = d;
210     }
211     if (a == 6 || a == 8) { // R ch or MONO
212       short *fifo = Pico32xMem->pwm_fifo[1];
213       if (Pico32x.pwm_p[1] < 3)
214         Pico32x.pwm_p[1]++;
215       else {
216         fifo[1] = fifo[2];
217         fifo[2] = fifo[3];
218       }
219       fifo[Pico32x.pwm_p[1]] = d;
220     }
221   }
222 }
223
224 void p32x_pwm_update(int *buf32, int length, int stereo)
225 {
226   short *pwmb;
227   int step;
228   int p = 0;
229   int xmd;
230
231   consume_fifo(NULL, SekCyclesDoneT2());
232
233   xmd = Pico32x.regs[0x30 / 2] & 0x0f;
234   if (xmd == 0 || xmd == 0x06 || xmd == 0x09 || xmd == 0x0f)
235     goto out; // invalid?
236
237   step = (pwm_ptr << 16) / length;
238   pwmb = Pico32xMem->pwm;
239
240   if (stereo)
241   {
242     if (xmd == 0x05) {
243       // normal
244       while (length-- > 0) {
245         *buf32++ += pwmb[0];
246         *buf32++ += pwmb[1];
247
248         p += step;
249         pwmb += (p >> 16) * 2;
250         p &= 0xffff;
251       }
252     }
253     else if (xmd == 0x0a) {
254       // channel swap
255       while (length-- > 0) {
256         *buf32++ += pwmb[1];
257         *buf32++ += pwmb[0];
258
259         p += step;
260         pwmb += (p >> 16) * 2;
261         p &= 0xffff;
262       }
263     }
264     else {
265       // mono - LMD, RMD specify dst
266       if (xmd & 0x06) // src is R
267         pwmb++;
268       if (xmd & 0x0c) // dst is R
269         buf32++;
270       while (length-- > 0) {
271         *buf32 += *pwmb;
272
273         p += step;
274         pwmb += (p >> 16) * 2;
275         p &= 0xffff;
276         buf32 += 2;
277       }
278     }
279   }
280   else
281   {
282     // mostly unused
283     while (length-- > 0) {
284       *buf32++ += pwmb[0];
285
286       p += step;
287       pwmb += (p >> 16) * 2;
288       p &= 0xffff;
289     }
290   }
291
292   elprintf(EL_PWM, "pwm_update: pwm_ptr %d, len %d, step %04x, done %d",
293     pwm_ptr, length, step, (pwmb - Pico32xMem->pwm) / 2);
294
295 out:
296   pwm_ptr = 0;
297 }
298
299 void p32x_pwm_state_loaded(void)
300 {
301   int cycles_diff_sh2;
302
303   p32x_pwm_ctl_changed();
304
305   // for old savestates
306   cycles_diff_sh2 = SekCycleCntT * 3 - Pico32x.pwm_cycle_p;
307   if (cycles_diff_sh2 >= pwm_cycles || cycles_diff_sh2 < 0) {
308     Pico32x.pwm_irq_cnt = pwm_irq_reload;
309     Pico32x.pwm_cycle_p = SekCycleCntT * 3;
310     p32x_pwm_schedule(SekCycleCntT);
311   }
312 }
313
314 // vim:shiftwidth=2:ts=2:expandtab