32x: move sh2 peripheral emu code to it's own file
[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
15 void p32x_pwm_ctl_changed(void)
16 {
17   int control = Pico32x.regs[0x30 / 2];
18   int cycles = Pico32x.regs[0x32 / 2];
19
20   cycles = (cycles - 1) & 0x0fff;
21   pwm_cycles = cycles;
22   pwm_mult = 0x10000 / cycles;
23
24   pwm_irq_reload = (control & 0x0f00) >> 8;
25   pwm_irq_reload = ((pwm_irq_reload - 1) & 0x0f) + 1;
26
27   if (Pico32x.pwm_irq_cnt == 0)
28     Pico32x.pwm_irq_cnt = pwm_irq_reload;
29 }
30
31 static void do_pwm_irq(unsigned int m68k_cycles)
32 {
33   Pico32x.sh2irqs |= P32XI_PWM;
34   p32x_update_irls(NULL);
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(m68k_cycles) { \
44   int cycles_diff = ((m68k_cycles) * 3) - Pico32x.pwm_cycle_p; \
45   if (cycles_diff >= pwm_cycles) \
46     consume_fifo_do(m68k_cycles, cycles_diff); \
47 }
48
49 static void consume_fifo_do(unsigned int m68k_cycles, int sh2_cycles_diff)
50 {
51   int do_irq = 0;
52
53   if (pwm_cycles == 0)
54     return;
55
56   elprintf(EL_PWM, "pwm: %u: consume %d/%d, %d,%d ptr %d",
57     m68k_cycles, sh2_cycles_diff, sh2_cycles_diff / pwm_cycles,
58     Pico32x.pwm_p[0], Pico32x.pwm_p[1], pwm_ptr);
59
60   if (sh2_cycles_diff >= pwm_cycles * 17) {
61     // silence/skip
62     Pico32x.pwm_cycle_p = m68k_cycles * 3;
63     Pico32x.pwm_p[0] = Pico32x.pwm_p[1] = 0;
64     return;
65   }
66
67   while (sh2_cycles_diff >= pwm_cycles) {
68     struct Pico32xMem *mem = Pico32xMem;
69     short *fifo_l = mem->pwm_fifo[0];
70     short *fifo_r = mem->pwm_fifo[1];
71
72     if (Pico32x.pwm_p[0] > 0) {
73       fifo_l[0] = fifo_l[1];
74       fifo_l[1] = fifo_l[2];
75       fifo_l[2] = fifo_l[3];
76       Pico32x.pwm_p[0]--;
77     }
78     if (Pico32x.pwm_p[1] > 0) {
79       fifo_r[0] = fifo_r[1];
80       fifo_r[1] = fifo_r[2];
81       fifo_r[2] = fifo_r[3];
82       Pico32x.pwm_p[1]--;
83     }
84
85     mem->pwm[pwm_ptr * 2    ] = fifo_l[0];
86     mem->pwm[pwm_ptr * 2 + 1] = fifo_r[0];
87     pwm_ptr = (pwm_ptr + 1) & (PWM_BUFF_LEN - 1);
88
89     sh2_cycles_diff -= pwm_cycles;
90
91     if (--Pico32x.pwm_irq_cnt == 0) {
92       Pico32x.pwm_irq_cnt = pwm_irq_reload;
93       // irq also does dreq1, so call it after cycle update
94       do_irq = 1;
95       break;
96     }
97   }
98   Pico32x.pwm_cycle_p = m68k_cycles * 3 - sh2_cycles_diff;
99
100   if (do_irq)
101     do_pwm_irq(m68k_cycles);
102 }
103
104 static int p32x_pwm_schedule_(unsigned int m68k_now)
105 {
106   unsigned int sh2_now = m68k_now * 3;
107   int cycles_diff_sh2;
108
109   if (pwm_cycles == 0)
110     return 0;
111
112   cycles_diff_sh2 = sh2_now - Pico32x.pwm_cycle_p;
113   if (cycles_diff_sh2 >= pwm_cycles)
114     consume_fifo_do(m68k_now, cycles_diff_sh2);
115
116   if (Pico32x.sh2irqs & P32XI_PWM)
117     return 0; // previous not acked
118   if (!((Pico32x.sh2irq_mask[0] | Pico32x.sh2irq_mask[1]) & 1))
119     return 0; // masked by everyone
120
121   cycles_diff_sh2 = sh2_now - Pico32x.pwm_cycle_p;
122   return (Pico32x.pwm_irq_cnt * pwm_cycles
123            - cycles_diff_sh2) / 3 + 1;
124 }
125
126 void p32x_pwm_schedule(unsigned int m68k_now)
127 {
128   int after = p32x_pwm_schedule_(m68k_now);
129   if (after != 0)
130     p32x_event_schedule(m68k_now, P32X_EVENT_PWM, after);
131 }
132
133 void p32x_pwm_schedule_sh2(SH2 *sh2)
134 {
135   int after = p32x_pwm_schedule_(sh2_cycles_done_m68k(sh2));
136   if (after != 0)
137     p32x_event_schedule_sh2(sh2, P32X_EVENT_PWM, after);
138 }
139
140 void p32x_pwm_irq_event(unsigned int m68k_now)
141 {
142   p32x_pwm_schedule(m68k_now);
143 }
144
145 unsigned int p32x_pwm_read16(unsigned int a, unsigned int m68k_cycles)
146 {
147   unsigned int d = 0;
148
149   consume_fifo(m68k_cycles);
150
151   a &= 0x0e;
152   switch (a) {
153     case 0: // control
154     case 2: // cycle
155       d = Pico32x.regs[(0x30 + a) / 2];
156       break;
157
158     case 4: // L ch
159       if (Pico32x.pwm_p[0] == 3)
160         d |= P32XP_FULL;
161       else if (Pico32x.pwm_p[0] == 0)
162         d |= P32XP_EMPTY;
163       break;
164
165     case 6: // R ch
166     case 8: // MONO
167       if (Pico32x.pwm_p[1] == 3)
168         d |= P32XP_FULL;
169       else if (Pico32x.pwm_p[1] == 0)
170         d |= P32XP_EMPTY;
171       break;
172   }
173
174   elprintf(EL_PWM, "pwm: %u: r16 %02x %04x (p %d %d)",
175     m68k_cycles, a, d, Pico32x.pwm_p[0], Pico32x.pwm_p[1]);
176   return d;
177 }
178
179 void p32x_pwm_write16(unsigned int a, unsigned int d,
180   unsigned int m68k_cycles)
181 {
182   elprintf(EL_PWM, "pwm: %u: w16 %02x %04x (p %d %d)",
183     m68k_cycles, a & 0x0e, d, Pico32x.pwm_p[0], Pico32x.pwm_p[1]);
184
185   consume_fifo(m68k_cycles);
186
187   a &= 0x0e;
188   if (a == 0) { // control
189     // supposedly we should stop FIFO when xMd is 0,
190     // but mars test disagrees
191     Pico32x.regs[0x30 / 2] = d;
192     p32x_pwm_ctl_changed();
193     Pico32x.pwm_irq_cnt = pwm_irq_reload; // ?
194   }
195   else if (a == 2) { // cycle
196     Pico32x.regs[0x32 / 2] = d & 0x0fff;
197     p32x_pwm_ctl_changed();
198   }
199   else if (a <= 8) {
200     d = (d - 1) & 0x0fff;
201     if (d > pwm_cycles)
202       d = pwm_cycles;
203     d = (d - pwm_cycles / 2) * pwm_mult;
204
205     if (a == 4 || a == 8) { // L ch or MONO
206       short *fifo = Pico32xMem->pwm_fifo[0];
207       if (Pico32x.pwm_p[0] < 3)
208         Pico32x.pwm_p[0]++;
209       else {
210         fifo[1] = fifo[2];
211         fifo[2] = fifo[3];
212       }
213       fifo[Pico32x.pwm_p[0]] = d;
214     }
215     if (a == 6 || a == 8) { // R ch or MONO
216       short *fifo = Pico32xMem->pwm_fifo[1];
217       if (Pico32x.pwm_p[1] < 3)
218         Pico32x.pwm_p[1]++;
219       else {
220         fifo[1] = fifo[2];
221         fifo[2] = fifo[3];
222       }
223       fifo[Pico32x.pwm_p[1]] = d;
224     }
225   }
226 }
227
228 void p32x_pwm_update(int *buf32, int length, int stereo)
229 {
230   short *pwmb;
231   int step;
232   int p = 0;
233   int xmd;
234
235   xmd = Pico32x.regs[0x30 / 2] & 0x0f;
236   if ((xmd != 0x05 && xmd != 0x0a) || pwm_ptr <= 16)
237     goto out;
238
239   step = (pwm_ptr << 16) / length; // FIXME: division..
240   pwmb = Pico32xMem->pwm;
241
242   if (stereo)
243   {
244     if (xmd == 0x0a) {
245       // channel swap
246       while (length-- > 0) {
247         *buf32++ += pwmb[1];
248         *buf32++ += pwmb[0];
249
250         p += step;
251         pwmb += (p >> 16) * 2;
252         p &= 0xffff;
253       }
254     }
255     else {
256       while (length-- > 0) {
257         *buf32++ += pwmb[0];
258         *buf32++ += pwmb[1];
259
260         p += step;
261         pwmb += (p >> 16) * 2;
262         p &= 0xffff;
263       }
264     }
265   }
266   else
267   {
268     while (length-- > 0) {
269       *buf32++ += pwmb[0];
270
271       p += step;
272       pwmb += (p >> 16) * 2;
273       p &= 0xffff;
274     }
275   }
276
277   elprintf(EL_PWM, "pwm_update: pwm_ptr %d, len %d, step %04x, done %d",
278     pwm_ptr, length, step, (pwmb - Pico32xMem->pwm) / 2);
279
280 out:
281   pwm_ptr = 0;
282 }
283
284 void p32x_pwm_state_loaded(void)
285 {
286   int cycles_diff_sh2;
287
288   p32x_pwm_ctl_changed();
289
290   // for old savestates
291   cycles_diff_sh2 = SekCycleCntT * 3 - Pico32x.pwm_cycle_p;
292   if (cycles_diff_sh2 >= pwm_cycles || cycles_diff_sh2 < 0) {
293     Pico32x.pwm_irq_cnt = pwm_irq_reload;
294     Pico32x.pwm_cycle_p = SekCycleCntT * 3;
295     p32x_pwm_schedule(SekCycleCntT);
296   }
297 }
298
299 // vim:shiftwidth=2:ts=2:expandtab