pandora: fix readme and pxml version
[picodrive.git] / pico / 32x / pwm.c
... / ...
CommitLineData
1/*
2 * PicoDrive
3 * (C) notaz, 2009,2010,2013
4 * (C) irixxxx, 2019-2023
5 *
6 * This work is licensed under the terms of MAME license.
7 * See COPYING file in the top-level directory.
8 */
9#include "../pico_int.h"
10
11static struct {
12 int cycles;
13 unsigned mult;
14 int ptr;
15 int irq_reload;
16 int doing_fifo;
17 int silent;
18 int irq_timer;
19 int irq_state;
20 short current[2];
21} pwm;
22
23enum { PWM_IRQ_LOCKED, PWM_IRQ_STOPPED, PWM_IRQ_LOW, PWM_IRQ_HIGH };
24
25void p32x_pwm_ctl_changed(void)
26{
27 int control = Pico32x.regs[0x30 / 2];
28 int cycles = Pico32x.regs[0x32 / 2];
29 int pwm_irq_opt = PicoIn.opt & POPT_PWM_IRQ_OPT;
30
31 cycles = (cycles - 1) & 0x0fff;
32 pwm.cycles = cycles;
33
34 // supposedly we should stop FIFO when xMd is 0,
35 // but mars test disagrees
36 pwm.mult = 0;
37 if ((control & 0x0f) != 0)
38 pwm.mult = (0x10000<<8) / (cycles+1);
39
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;
44
45 if (Pico32x.pwm_irq_cnt <= 0)
46 Pico32x.pwm_irq_cnt = pwm.irq_reload;
47}
48
49static void do_pwm_irq(SH2 *sh2, unsigned int m68k_cycles)
50{
51 p32x_trigger_irq(NULL, m68k_cycles, P32XI_PWM);
52
53 if (Pico32x.regs[0x30 / 2] & P32XP_RTP) {
54 p32x_event_schedule(m68k_cycles, P32X_EVENT_PWM, pwm.cycles / 3 + 1);
55 // note: might recurse
56 p32x_dreq1_trigger();
57 }
58}
59
60static int convert_sample(unsigned int v)
61{
62 if (v > pwm.cycles)
63 v = pwm.cycles;
64 return (v * pwm.mult >> 8) - 0x10000/2;
65}
66
67#define consume_fifo(sh2, m68k_cycles) { \
68 int cycles_diff = ((m68k_cycles) * 3) - Pico32x.pwm_cycle_p; \
69 if (cycles_diff >= pwm.cycles) \
70 consume_fifo_do(sh2, m68k_cycles, cycles_diff); \
71}
72
73static void consume_fifo_do(SH2 *sh2, unsigned int m68k_cycles,
74 int sh2_cycles_diff)
75{
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
81 if (pwm.cycles == 0 || pwm.doing_fifo)
82 return;
83
84 elprintf(EL_PWM, "pwm: %u: consume %d/%d, %d,%d ptr %d",
85 m68k_cycles, sh2_cycles_diff, sh2_cycles_diff / pwm.cycles,
86 Pico32x.pwm_p[0], Pico32x.pwm_p[1], pwm.ptr);
87
88 // this is for recursion from dreq1 writes
89 pwm.doing_fifo = 1;
90
91 while (sh2_cycles_diff >= pwm.cycles)
92 {
93 sh2_cycles_diff -= pwm.cycles;
94
95 if (Pico32x.pwm_p[0] > 0) {
96 mem->pwm_index[0] = (mem->pwm_index[0]+1) % 4;
97 Pico32x.pwm_p[0]--;
98 pwm.current[0] = convert_sample(fifo_l[mem->pwm_index[0]]);
99 sum |= (u16)pwm.current[0];
100 }
101 if (Pico32x.pwm_p[1] > 0) {
102 mem->pwm_index[1] = (mem->pwm_index[1]+1) % 4;
103 Pico32x.pwm_p[1]--;
104 pwm.current[1] = convert_sample(fifo_r[mem->pwm_index[1]]);
105 sum |= (u16)pwm.current[1];
106 }
107
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);
111
112 if (--Pico32x.pwm_irq_cnt <= 0) {
113 Pico32x.pwm_irq_cnt = pwm.irq_reload;
114 do_pwm_irq(sh2, m68k_cycles);
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;
120 }
121 }
122 Pico32x.pwm_cycle_p = m68k_cycles * 3 - sh2_cycles_diff;
123 pwm.doing_fifo = 0;
124 if (sum != 0)
125 pwm.silent = 0;
126}
127
128static int p32x_pwm_schedule_(SH2 *sh2, unsigned int m68k_now)
129{
130 unsigned int pwm_now = m68k_now * 3;
131 int cycles_diff_sh2;
132
133 if (pwm.cycles == 0)
134 return 0;
135
136 cycles_diff_sh2 = pwm_now - Pico32x.pwm_cycle_p;
137 if (cycles_diff_sh2 >= pwm.cycles)
138 consume_fifo_do(sh2, m68k_now, cycles_diff_sh2);
139
140 if (!((Pico32x.sh2irq_mask[0] | Pico32x.sh2irq_mask[1]) & 1))
141 return 0; // masked by everyone
142
143 cycles_diff_sh2 = pwm_now - Pico32x.pwm_cycle_p;
144 return (Pico32x.pwm_irq_cnt * pwm.cycles
145 - cycles_diff_sh2) / 3 + 1;
146}
147
148void p32x_pwm_schedule(unsigned int m68k_now)
149{
150 int after = p32x_pwm_schedule_(NULL, m68k_now);
151 if (after != 0)
152 p32x_event_schedule(m68k_now, P32X_EVENT_PWM, after);
153}
154
155void p32x_pwm_schedule_sh2(SH2 *sh2)
156{
157 int after = p32x_pwm_schedule_(sh2, sh2_cycles_done_m68k(sh2));
158 if (after != 0)
159 p32x_event_schedule_sh2(sh2, P32X_EVENT_PWM, after);
160}
161
162void 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
168void p32x_pwm_irq_event(unsigned int m68k_now)
169{
170 p32x_pwm_schedule(m68k_now);
171}
172
173unsigned int p32x_pwm_read16(u32 a, SH2 *sh2, unsigned int m68k_cycles)
174{
175 unsigned int d = 0;
176
177 consume_fifo(sh2, m68k_cycles);
178
179 a &= 0x0e;
180 switch (a/2) {
181 case 0/2: // control
182 case 2/2: // cycle
183 d = Pico32x.regs[(0x30 + a) / 2];
184 break;
185
186 case 4/2: // L ch
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
193 case 6/2: // R ch
194 case 8/2: // MONO
195 if (Pico32x.pwm_p[1] == 3)
196 d |= P32XP_FULL;
197 else if (Pico32x.pwm_p[1] == 0)
198 d |= P32XP_EMPTY;
199 break;
200 }
201
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]);
204 return d;
205}
206
207void p32x_pwm_write16(u32 a, unsigned int d, SH2 *sh2, unsigned int m68k_cycles)
208{
209 unsigned short *fifo;
210 int idx;
211
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
215 consume_fifo(sh2, m68k_cycles);
216
217 a &= 0x0e;
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];
235 if (Pico32x.pwm_p[1] < 3) {
236 if (Pico32x.pwm_p[1] == 2 && pwm.irq_state >= PWM_IRQ_STOPPED) {
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 }
243 Pico32x.pwm_p[1]++;
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;
249 idx = (idx+1) % 4;
250 Pico32xMem->pwm_index[1] = idx;
251 }
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 {
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;
265 }
266}
267
268void p32x_pwm_update(s32 *buf32, int length, int stereo)
269{
270 short *pwmb;
271 int step;
272 int p = 0;
273 int xmd;
274
275 consume_fifo(NULL, SekCyclesDone());
276
277 xmd = Pico32x.regs[0x30 / 2] & 0x0f;
278 if (xmd == 0 || xmd == 0x06 || xmd == 0x09 || xmd == 0x0f)
279 goto out; // invalid?
280 if (pwm.silent)
281 return;
282
283 step = (pwm.ptr << 16) / length;
284 pwmb = Pico32xMem->pwm;
285
286 if (stereo)
287 {
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) {
300 // channel swap
301 while (length-- > 0) {
302 *buf32++ += pwmb[1];
303 *buf32++ += pwmb[0];
304
305 p += step;
306 pwmb += (p >> 16) * 2;
307 p &= 0xffff;
308 }
309 }
310 else {
311 // mono - LMD, RMD specify dst
312 if (xmd & 0x06) // src is R
313 pwmb++;
314 if (xmd & 0x0c) // dst is R
315 buf32++;
316 while (length-- > 0) {
317 *buf32 += *pwmb;
318
319 p += step;
320 pwmb += (p >> 16) * 2;
321 p &= 0xffff;
322 buf32 += 2;
323 }
324 }
325 }
326 else
327 {
328 // mostly unused
329 while (length-- > 0) {
330 *buf32++ += pwmb[0];
331
332 p += step;
333 pwmb += (p >> 16) * 2;
334 p &= 0xffff;
335 }
336 }
337
338 elprintf(EL_PWM, "pwm_update: pwm.ptr %d, len %d, step %04x, done %d",
339 pwm.ptr, length, step, (pwmb - Pico32xMem->pwm) / 2);
340
341out:
342 pwm.ptr = 0;
343 pwm.silent = pwm.current[0] == 0 && pwm.current[1] == 0;
344}
345
346void p32x_pwm_state_loaded(void)
347{
348 int cycles_diff_sh2;
349
350 p32x_pwm_ctl_changed();
351
352 // for old savestates
353 cycles_diff_sh2 = Pico.t.m68c_cnt * 3 - Pico32x.pwm_cycle_p;
354 if (cycles_diff_sh2 >= pwm.cycles || cycles_diff_sh2 < 0) {
355 Pico32x.pwm_irq_cnt = pwm.irq_reload;
356 Pico32x.pwm_cycle_p = Pico.t.m68c_cnt * 3;
357 p32x_pwm_schedule(Pico.t.m68c_cnt);
358 }
359}
360
361// vim:shiftwidth=2:ts=2:expandtab