cff531af |
1 | /* |
2 | * PicoDrive |
a7f82a77 |
3 | * (C) notaz, 2009,2010,2013 |
7bf552b5 |
4 | * (C) irixxxx, 2019-2023 |
cff531af |
5 | * |
6 | * This work is licensed under the terms of MAME license. |
7 | * See COPYING file in the top-level directory. |
8 | */ |
db1d3564 |
9 | #include "../pico_int.h" |
10 | |
fe344bd3 |
11 | static struct { |
12 | int cycles; |
bb0488a6 |
13 | unsigned mult; |
fe344bd3 |
14 | int ptr; |
15 | int irq_reload; |
16 | int doing_fifo; |
17 | int silent; |
20d2358a |
18 | int irq_timer; |
19 | int irq_state; |
fe344bd3 |
20 | short current[2]; |
21 | } pwm; |
db1d3564 |
22 | |
20d2358a |
23 | enum { PWM_IRQ_LOCKED, PWM_IRQ_STOPPED, PWM_IRQ_LOW, PWM_IRQ_HIGH }; |
24 | |
045a4c52 |
25 | void p32x_pwm_ctl_changed(void) |
db1d3564 |
26 | { |
df63f1a6 |
27 | int control = Pico32x.regs[0x30 / 2]; |
db1d3564 |
28 | int cycles = Pico32x.regs[0x32 / 2]; |
20d2358a |
29 | int pwm_irq_opt = PicoIn.opt & POPT_PWM_IRQ_OPT; |
db1d3564 |
30 | |
31 | cycles = (cycles - 1) & 0x0fff; |
fe344bd3 |
32 | pwm.cycles = cycles; |
8ce9c3a7 |
33 | |
34 | // supposedly we should stop FIFO when xMd is 0, |
35 | // but mars test disagrees |
fe344bd3 |
36 | pwm.mult = 0; |
8ce9c3a7 |
37 | if ((control & 0x0f) != 0) |
adffea8d |
38 | pwm.mult = (0x10000<<8) / (cycles+1); |
1d7a28a7 |
39 | |
20d2358a |
40 | pwm.irq_timer = (control & 0x0f00) >> 8; |
41 | pwm.irq_timer = ((pwm.irq_timer - 1) & 0x0f) + 1; |
42 | pwm.irq_reload = pwm.irq_timer; |
43 | pwm.irq_state = pwm_irq_opt ? PWM_IRQ_STOPPED: PWM_IRQ_LOCKED; |
df63f1a6 |
44 | |
7b02a2c3 |
45 | if (Pico32x.pwm_irq_cnt <= 0) |
fe344bd3 |
46 | Pico32x.pwm_irq_cnt = pwm.irq_reload; |
db1d3564 |
47 | } |
48 | |
c1931173 |
49 | static void do_pwm_irq(SH2 *sh2, unsigned int m68k_cycles) |
df63f1a6 |
50 | { |
7b02a2c3 |
51 | p32x_trigger_irq(NULL, m68k_cycles, P32XI_PWM); |
df63f1a6 |
52 | |
53 | if (Pico32x.regs[0x30 / 2] & P32XP_RTP) { |
fe344bd3 |
54 | p32x_event_schedule(m68k_cycles, P32X_EVENT_PWM, pwm.cycles / 3 + 1); |
df63f1a6 |
55 | // note: might recurse |
56 | p32x_dreq1_trigger(); |
57 | } |
58 | } |
59 | |
8ce9c3a7 |
60 | static int convert_sample(unsigned int v) |
61 | { |
fe344bd3 |
62 | if (v > pwm.cycles) |
63 | v = pwm.cycles; |
adffea8d |
64 | return (v * pwm.mult >> 8) - 0x10000/2; |
8ce9c3a7 |
65 | } |
66 | |
c1931173 |
67 | #define consume_fifo(sh2, m68k_cycles) { \ |
df63f1a6 |
68 | int cycles_diff = ((m68k_cycles) * 3) - Pico32x.pwm_cycle_p; \ |
fe344bd3 |
69 | if (cycles_diff >= pwm.cycles) \ |
c1931173 |
70 | consume_fifo_do(sh2, m68k_cycles, cycles_diff); \ |
a7f82a77 |
71 | } |
72 | |
c1931173 |
73 | static void consume_fifo_do(SH2 *sh2, unsigned int m68k_cycles, |
74 | int sh2_cycles_diff) |
db1d3564 |
75 | { |
8ce9c3a7 |
76 | struct Pico32xMem *mem = Pico32xMem; |
77 | unsigned short *fifo_l = mem->pwm_fifo[0]; |
78 | unsigned short *fifo_r = mem->pwm_fifo[1]; |
79 | int sum = 0; |
80 | |
fe344bd3 |
81 | if (pwm.cycles == 0 || pwm.doing_fifo) |
a7f82a77 |
82 | return; |
83 | |
84 | elprintf(EL_PWM, "pwm: %u: consume %d/%d, %d,%d ptr %d", |
fe344bd3 |
85 | m68k_cycles, sh2_cycles_diff, sh2_cycles_diff / pwm.cycles, |
86 | Pico32x.pwm_p[0], Pico32x.pwm_p[1], pwm.ptr); |
a7f82a77 |
87 | |
8ad1d2ad |
88 | // this is for recursion from dreq1 writes |
fe344bd3 |
89 | pwm.doing_fifo = 1; |
07e5dbab |
90 | |
bb0488a6 |
91 | while (sh2_cycles_diff >= pwm.cycles) |
8ce9c3a7 |
92 | { |
bb0488a6 |
93 | sh2_cycles_diff -= pwm.cycles; |
94 | |
a7f82a77 |
95 | if (Pico32x.pwm_p[0] > 0) { |
fe344bd3 |
96 | mem->pwm_index[0] = (mem->pwm_index[0]+1) % 4; |
a7f82a77 |
97 | Pico32x.pwm_p[0]--; |
fe344bd3 |
98 | pwm.current[0] = convert_sample(fifo_l[mem->pwm_index[0]]); |
bb0488a6 |
99 | sum |= (u16)pwm.current[0]; |
bc3aea8e |
100 | } |
a7f82a77 |
101 | if (Pico32x.pwm_p[1] > 0) { |
fe344bd3 |
102 | mem->pwm_index[1] = (mem->pwm_index[1]+1) % 4; |
a7f82a77 |
103 | Pico32x.pwm_p[1]--; |
fe344bd3 |
104 | pwm.current[1] = convert_sample(fifo_r[mem->pwm_index[1]]); |
bb0488a6 |
105 | sum |= (u16)pwm.current[1]; |
a7f82a77 |
106 | } |
107 | |
fe344bd3 |
108 | mem->pwm[pwm.ptr * 2 ] = pwm.current[0]; |
109 | mem->pwm[pwm.ptr * 2 + 1] = pwm.current[1]; |
110 | pwm.ptr = (pwm.ptr + 1) & (PWM_BUFF_LEN - 1); |
df63f1a6 |
111 | |
7b02a2c3 |
112 | if (--Pico32x.pwm_irq_cnt <= 0) { |
fe344bd3 |
113 | Pico32x.pwm_irq_cnt = pwm.irq_reload; |
8ad1d2ad |
114 | do_pwm_irq(sh2, m68k_cycles); |
20d2358a |
115 | } else if (Pico32x.pwm_p[1] == 0 && pwm.irq_state >= PWM_IRQ_LOW) { |
116 | // buffer underrun. Reduce reload rate if above programmed setting. |
117 | if (pwm.irq_reload > pwm.irq_timer) |
118 | pwm.irq_reload--; |
119 | pwm.irq_state = PWM_IRQ_LOW; |
df63f1a6 |
120 | } |
a8fd6e37 |
121 | } |
df63f1a6 |
122 | Pico32x.pwm_cycle_p = m68k_cycles * 3 - sh2_cycles_diff; |
fe344bd3 |
123 | pwm.doing_fifo = 0; |
8ce9c3a7 |
124 | if (sum != 0) |
fe344bd3 |
125 | pwm.silent = 0; |
a7f82a77 |
126 | } |
127 | |
c1931173 |
128 | static int p32x_pwm_schedule_(SH2 *sh2, unsigned int m68k_now) |
a8fd6e37 |
129 | { |
fe344bd3 |
130 | unsigned int pwm_now = m68k_now * 3; |
df63f1a6 |
131 | int cycles_diff_sh2; |
132 | |
fe344bd3 |
133 | if (pwm.cycles == 0) |
df63f1a6 |
134 | return 0; |
135 | |
fe344bd3 |
136 | cycles_diff_sh2 = pwm_now - Pico32x.pwm_cycle_p; |
137 | if (cycles_diff_sh2 >= pwm.cycles) |
c1931173 |
138 | consume_fifo_do(sh2, m68k_now, cycles_diff_sh2); |
a8fd6e37 |
139 | |
a8fd6e37 |
140 | if (!((Pico32x.sh2irq_mask[0] | Pico32x.sh2irq_mask[1]) & 1)) |
19886062 |
141 | return 0; // masked by everyone |
a8fd6e37 |
142 | |
fe344bd3 |
143 | cycles_diff_sh2 = pwm_now - Pico32x.pwm_cycle_p; |
144 | return (Pico32x.pwm_irq_cnt * pwm.cycles |
df63f1a6 |
145 | - cycles_diff_sh2) / 3 + 1; |
19886062 |
146 | } |
147 | |
df63f1a6 |
148 | void p32x_pwm_schedule(unsigned int m68k_now) |
19886062 |
149 | { |
c1931173 |
150 | int after = p32x_pwm_schedule_(NULL, m68k_now); |
19886062 |
151 | if (after != 0) |
df63f1a6 |
152 | p32x_event_schedule(m68k_now, P32X_EVENT_PWM, after); |
19886062 |
153 | } |
154 | |
155 | void p32x_pwm_schedule_sh2(SH2 *sh2) |
156 | { |
c1931173 |
157 | int after = p32x_pwm_schedule_(sh2, sh2_cycles_done_m68k(sh2)); |
19886062 |
158 | if (after != 0) |
159 | p32x_event_schedule_sh2(sh2, P32X_EVENT_PWM, after); |
a8fd6e37 |
160 | } |
161 | |
9e1fa0a6 |
162 | void p32x_pwm_sync_to_sh2(SH2 *sh2) |
163 | { |
164 | int m68k_cycles = sh2_cycles_done_m68k(sh2); |
165 | consume_fifo(sh2, m68k_cycles); |
166 | } |
167 | |
df63f1a6 |
168 | void p32x_pwm_irq_event(unsigned int m68k_now) |
169 | { |
170 | p32x_pwm_schedule(m68k_now); |
171 | } |
172 | |
15eed405 |
173 | unsigned int p32x_pwm_read16(u32 a, SH2 *sh2, unsigned int m68k_cycles) |
db1d3564 |
174 | { |
175 | unsigned int d = 0; |
a7f82a77 |
176 | |
c1931173 |
177 | consume_fifo(sh2, m68k_cycles); |
db1d3564 |
178 | |
179 | a &= 0x0e; |
fe344bd3 |
180 | switch (a/2) { |
181 | case 0/2: // control |
182 | case 2/2: // cycle |
a7f82a77 |
183 | d = Pico32x.regs[(0x30 + a) / 2]; |
184 | break; |
db1d3564 |
185 | |
fe344bd3 |
186 | case 4/2: // L ch |
a7f82a77 |
187 | if (Pico32x.pwm_p[0] == 3) |
188 | d |= P32XP_FULL; |
189 | else if (Pico32x.pwm_p[0] == 0) |
190 | d |= P32XP_EMPTY; |
191 | break; |
192 | |
fe344bd3 |
193 | case 6/2: // R ch |
194 | case 8/2: // MONO |
a7f82a77 |
195 | if (Pico32x.pwm_p[1] == 3) |
db1d3564 |
196 | d |= P32XP_FULL; |
a7f82a77 |
197 | else if (Pico32x.pwm_p[1] == 0) |
db1d3564 |
198 | d |= P32XP_EMPTY; |
199 | break; |
200 | } |
201 | |
df63f1a6 |
202 | elprintf(EL_PWM, "pwm: %u: r16 %02x %04x (p %d %d)", |
203 | m68k_cycles, a, d, Pico32x.pwm_p[0], Pico32x.pwm_p[1]); |
db1d3564 |
204 | return d; |
205 | } |
206 | |
15eed405 |
207 | void p32x_pwm_write16(u32 a, unsigned int d, SH2 *sh2, unsigned int m68k_cycles) |
db1d3564 |
208 | { |
fe344bd3 |
209 | unsigned short *fifo; |
210 | int idx; |
211 | |
df63f1a6 |
212 | elprintf(EL_PWM, "pwm: %u: w16 %02x %04x (p %d %d)", |
213 | m68k_cycles, a & 0x0e, d, Pico32x.pwm_p[0], Pico32x.pwm_p[1]); |
214 | |
c1931173 |
215 | consume_fifo(sh2, m68k_cycles); |
a7f82a77 |
216 | |
db1d3564 |
217 | a &= 0x0e; |
fe344bd3 |
218 | switch (a/2) { |
219 | case 0/2: // control |
220 | // avoiding pops.. |
221 | if ((Pico32x.regs[0x30 / 2] & 0x0f) == 0) |
222 | Pico32xMem->pwm_fifo[0][0] = Pico32xMem->pwm_fifo[1][0] = 0; |
223 | Pico32x.regs[0x30 / 2] = d; |
224 | p32x_pwm_ctl_changed(); |
225 | Pico32x.pwm_irq_cnt = pwm.irq_reload; // ? |
226 | break; |
227 | case 2/2: // cycle |
228 | Pico32x.regs[0x32 / 2] = d & 0x0fff; |
229 | p32x_pwm_ctl_changed(); |
230 | break; |
231 | case 8/2: // MONO |
232 | case 6/2: // R ch |
233 | fifo = Pico32xMem->pwm_fifo[1]; |
234 | idx = Pico32xMem->pwm_index[1]; |
20d2358a |
235 | if (Pico32x.pwm_p[1] < 3) { |
bb0488a6 |
236 | if (Pico32x.pwm_p[1] == 2 && pwm.irq_state >= PWM_IRQ_STOPPED) { |
20d2358a |
237 | // buffer full. If there was no buffer underrun after last fill, |
238 | // try increasing reload rate to reduce IRQs |
239 | if (pwm.irq_reload < 3 && pwm.irq_state == PWM_IRQ_HIGH) |
240 | pwm.irq_reload ++; |
241 | pwm.irq_state = PWM_IRQ_HIGH; |
242 | } |
a7f82a77 |
243 | Pico32x.pwm_p[1]++; |
20d2358a |
244 | } else { |
245 | // buffer overflow. Some roms always fill the complete buffer even if |
246 | // reload rate is set below max. Lock reload rate to programmed setting. |
247 | pwm.irq_reload = pwm.irq_timer; |
248 | pwm.irq_state = PWM_IRQ_LOCKED; |
fe344bd3 |
249 | idx = (idx+1) % 4; |
bb0488a6 |
250 | Pico32xMem->pwm_index[1] = idx; |
a7f82a77 |
251 | } |
fe344bd3 |
252 | fifo[(idx+Pico32x.pwm_p[1]) % 4] = (d - 1) & 0x0fff; |
253 | if (a != 8) break; // fallthrough if MONO |
254 | case 4/2: // L ch |
255 | fifo = Pico32xMem->pwm_fifo[0]; |
256 | idx = Pico32xMem->pwm_index[0]; |
257 | if (Pico32x.pwm_p[0] < 3) |
258 | Pico32x.pwm_p[0]++; |
259 | else { |
fe344bd3 |
260 | idx = (idx+1) % 4; |
261 | Pico32xMem->pwm_index[0] = idx; |
262 | } |
263 | fifo[(idx+Pico32x.pwm_p[0]) % 4] = (d - 1) & 0x0fff; |
264 | break; |
db1d3564 |
265 | } |
266 | } |
267 | |
f7741cac |
268 | void p32x_pwm_update(s32 *buf32, int length, int stereo) |
db1d3564 |
269 | { |
db1d3564 |
270 | short *pwmb; |
271 | int step; |
272 | int p = 0; |
a7f82a77 |
273 | int xmd; |
db1d3564 |
274 | |
ae214f1c |
275 | consume_fifo(NULL, SekCyclesDone()); |
8ad1d2ad |
276 | |
a7f82a77 |
277 | xmd = Pico32x.regs[0x30 / 2] & 0x0f; |
8ad1d2ad |
278 | if (xmd == 0 || xmd == 0x06 || xmd == 0x09 || xmd == 0x0f) |
279 | goto out; // invalid? |
fe344bd3 |
280 | if (pwm.silent) |
8ce9c3a7 |
281 | return; |
db1d3564 |
282 | |
fe344bd3 |
283 | step = (pwm.ptr << 16) / length; |
db1d3564 |
284 | pwmb = Pico32xMem->pwm; |
285 | |
286 | if (stereo) |
287 | { |
8ad1d2ad |
288 | if (xmd == 0x05) { |
289 | // normal |
290 | while (length-- > 0) { |
291 | *buf32++ += pwmb[0]; |
292 | *buf32++ += pwmb[1]; |
293 | |
294 | p += step; |
295 | pwmb += (p >> 16) * 2; |
296 | p &= 0xffff; |
297 | } |
298 | } |
299 | else if (xmd == 0x0a) { |
a7f82a77 |
300 | // channel swap |
301 | while (length-- > 0) { |
302 | *buf32++ += pwmb[1]; |
303 | *buf32++ += pwmb[0]; |
db1d3564 |
304 | |
a7f82a77 |
305 | p += step; |
306 | pwmb += (p >> 16) * 2; |
307 | p &= 0xffff; |
308 | } |
309 | } |
310 | else { |
8ad1d2ad |
311 | // mono - LMD, RMD specify dst |
312 | if (xmd & 0x06) // src is R |
313 | pwmb++; |
314 | if (xmd & 0x0c) // dst is R |
315 | buf32++; |
a7f82a77 |
316 | while (length-- > 0) { |
8ad1d2ad |
317 | *buf32 += *pwmb; |
a7f82a77 |
318 | |
319 | p += step; |
320 | pwmb += (p >> 16) * 2; |
321 | p &= 0xffff; |
8ad1d2ad |
322 | buf32 += 2; |
a7f82a77 |
323 | } |
db1d3564 |
324 | } |
325 | } |
326 | else |
327 | { |
8ad1d2ad |
328 | // mostly unused |
db1d3564 |
329 | while (length-- > 0) { |
330 | *buf32++ += pwmb[0]; |
331 | |
332 | p += step; |
333 | pwmb += (p >> 16) * 2; |
334 | p &= 0xffff; |
335 | } |
336 | } |
337 | |
fe344bd3 |
338 | elprintf(EL_PWM, "pwm_update: pwm.ptr %d, len %d, step %04x, done %d", |
339 | pwm.ptr, length, step, (pwmb - Pico32xMem->pwm) / 2); |
db1d3564 |
340 | |
a7f82a77 |
341 | out: |
fe344bd3 |
342 | pwm.ptr = 0; |
343 | pwm.silent = pwm.current[0] == 0 && pwm.current[1] == 0; |
db1d3564 |
344 | } |
345 | |
df63f1a6 |
346 | void p32x_pwm_state_loaded(void) |
347 | { |
348 | int cycles_diff_sh2; |
349 | |
045a4c52 |
350 | p32x_pwm_ctl_changed(); |
df63f1a6 |
351 | |
352 | // for old savestates |
88fd63ad |
353 | cycles_diff_sh2 = Pico.t.m68c_cnt * 3 - Pico32x.pwm_cycle_p; |
fe344bd3 |
354 | if (cycles_diff_sh2 >= pwm.cycles || cycles_diff_sh2 < 0) { |
355 | Pico32x.pwm_irq_cnt = pwm.irq_reload; |
88fd63ad |
356 | Pico32x.pwm_cycle_p = Pico.t.m68c_cnt * 3; |
357 | p32x_pwm_schedule(Pico.t.m68c_cnt); |
df63f1a6 |
358 | } |
359 | } |
360 | |
a8fd6e37 |
361 | // vim:shiftwidth=2:ts=2:expandtab |