pandora: fix readme and pxml version
[picodrive.git] / pico / 32x / pwm.c
CommitLineData
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 11static 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 23enum { PWM_IRQ_LOCKED, PWM_IRQ_STOPPED, PWM_IRQ_LOW, PWM_IRQ_HIGH };
24
045a4c52 25void 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 49static 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 60static 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 73static 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 128static 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 148void 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
155void 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 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
df63f1a6 168void p32x_pwm_irq_event(unsigned int m68k_now)
169{
170 p32x_pwm_schedule(m68k_now);
171}
172
15eed405 173unsigned 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 207void 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 268void 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 341out:
fe344bd3 342 pwm.ptr = 0;
343 pwm.silent = pwm.current[0] == 0 && pwm.current[1] == 0;
db1d3564 344}
345
df63f1a6 346void 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