583b258361bec2797cce7ea1ee389a85daaac367
[picodrive.git] / pico / videoport.c
1 /*\r
2  * PicoDrive\r
3  * (c) Copyright Dave, 2004\r
4  * (C) notaz, 2006-2009\r
5  * (C) irixxxx, 2020-2024\r
6  *\r
7  * This work is licensed under the terms of MAME license.\r
8  * See COPYING file in the top-level directory.\r
9  */\r
10 \r
11 #include "pico_int.h"\r
12 #define NEED_DMA_SOURCE\r
13 #include "memory.h"\r
14 \r
15 \r
16 enum { clkdiv = 2 };    // CPU clock granularity: one of 1,2,4,8\r
17 \r
18 // VDP Slot timing, taken from http://gendev.spritesmind.net/\r
19 //     forum/viewtopic.php?f=22&t=851&sid=d5701a71396ee7f700c74fb7cd85cb09\r
20 //     http://plutiedev.com/mirror/kabuto-hardware-notes\r
21 // Thank you very much for the great work, Nemesis, Kabuto!\r
22 \r
23 // Slot clock is sysclock/20 for h32 and sysclock/16 for h40.\r
24 // One scanline is 63.7us/64.3us (ntsc/pal) long which is ~488.57 68k cycles.\r
25 // Approximate by 488 for VDP.\r
26 // 1 slot is 20/7 = 2.857 68k cycles in h32, and 16/7 = 2.286 in h40. That's\r
27 // 171 slots in h32, and ~214 (really 193 plus 17 prolonged in HSYNC) in h40.\r
28 enum { slcpu = 488 };\r
29 \r
30 // VDP has a slot counter running from 0x00 to 0xff every scanline, but it has\r
31 // a gap depending on the video mode. The slot in which a horizontal interrupt\r
32 // is generated also depends on the video mode.\r
33 // NB Kabuto says gapend40 is 0xe4. That's technically correct, since slots 0xb6\r
34 // and 0xe4 are only half slots. Ignore 0xe4 here and make 0xb6 a full slot.\r
35 enum { hint32 = 0x85, gapstart32 = 0x94, gapend32 = 0xe9};\r
36 enum { hint40 = 0xa5, gapstart40 = 0xb7, gapend40 = 0xe5};\r
37 \r
38 // Basic timing in h32: 38 slots (~108.5 cycles) from hint to VDP output start\r
39 // at slot 0x00. vint takes place on the 1st VBLANK line in slot 0x01 (~111.5).\r
40 // Rendering takes 128 slots (~365.5), and right border starts at slot 0x80\r
41 // (~474 cycles). hint occurs after 5 slots into the border (~488.5 cycles).\r
42 \r
43 // The horizontal sync period (HBLANK) is 30/37 slots (h32/h40):\r
44 // h32: 4 slots front porch (1.49us), 13 HSYNC (4.84us), 13 back porch (4.84us)\r
45 // h40: 5 slots front porch (1.49us), 16 HSYNC (4.77us), 16 back porch (4.77us)\r
46 // HBLANK starts at slot 0x93/0xb4 and ends in the middle of slot 0x05/0x06,\r
47 // NB VDP slows down the h40 clock to h32 during HSYNC for 17 slots to get the\r
48 // right sync timing. Ignored in the slot calculation, but hblen40 is correct.\r
49 enum { hboff32 = 0x93-hint32, hblen32 = 0xf8-(gapend32-gapstart32)-hint32};//30\r
50 enum { hboff40 = 0xb4-hint40, hblen40 = 0xf8-(gapend40-gapstart40)-hint40};//37\r
51 \r
52 // number of slots in a scanline\r
53 #define slots32 (0x100-(gapend32-gapstart32)) // 171\r
54 #define slots40 (0x100-(gapend40-gapstart40)) // 210\r
55 \r
56 // In blanked display, all slots but the refresh slots are usable for transfers,\r
57 // in active display only 16(h32) / 18(h40) slots can be used.\r
58 \r
59 // dma and refresh slots for active display, 16 for H32\r
60 static u8 dmaslots32[] =\r
61     { 145,243, 2,10,18, 34,42,50, 66,74,82, 98,106,114, 129,130 };\r
62 static u8 refslots32[] =\r
63     {        250,     26,       58,       90,         122 };\r
64 // dma and refresh slots for active display, 18 for H40\r
65 static u8 dmaslots40[] =\r
66     {     232, 2,10,18, 34,42,50, 66,74,82, 98,106,114, 130,138,146, 161,162 };\r
67 static u8 refslots40[] =\r
68     {        250,     26,       58,       90,         122,         154 };\r
69 \r
70 // table sizes\r
71 enum { cycsz = slcpu/clkdiv };\r
72 enum { sl32blsz=slots32-sizeof(refslots32)+1, sl32acsz=sizeof(dmaslots32)+1 };\r
73 enum { sl40blsz=slots40-sizeof(refslots40)+1, sl40acsz=sizeof(dmaslots40)+1 };\r
74 \r
75 // Tables must be considerably larger than one scanline, since 68k emulation\r
76 // isn't stopping in the middle of an operation. If the last op is a 32 bit\r
77 // VDP access 2 slots may need to be taken from the next scanline, which can be\r
78 // more than 100 CPU cycles. For safety just cover 2 scanlines.\r
79 \r
80 // table for hvcounter mapping. check: Sonic 3D Blast bonus, Cannon Fodder,\r
81 // Chase HQ II, 3 Ninjas kick back, Road Rash 3, Skitchin', Wheel of Fortune\r
82 static u8  hcounts_32[2*cycsz], hcounts_40[2*cycsz];\r
83 // tables mapping cycles to slots\r
84 static u16 vdpcyc2sl_32_bl[2*cycsz],vdpcyc2sl_40_bl[2*cycsz];\r
85 static u16 vdpcyc2sl_32_ac[2*cycsz],vdpcyc2sl_40_ac[2*cycsz];\r
86 // tables mapping slots to cycles\r
87 // NB the sl2cyc tables must cover all slots present in the cyc2sl tables.\r
88 static u16 vdpsl2cyc_32_bl[2*sl32blsz],vdpsl2cyc_40_bl[2*sl40blsz];\r
89 static u16 vdpsl2cyc_32_ac[2*sl32acsz],vdpsl2cyc_40_ac[2*sl40acsz];\r
90 \r
91 \r
92 // calculate timing tables for one mode (H32 or H40)\r
93 // NB tables aligned to HINT, since the main loop uses HINT as synchronization\r
94 #define INITTABLES(s) { \\r
95   float factor = (float)slcpu/slots##s;                                 \\r
96   int ax, bx, rx, ac, bc;                                               \\r
97   int i, n;                                                             \\r
98                                                                         \\r
99   /* calculate internal VDP slot numbers */                             \\r
100   for (i = 0; i < cycsz; i++) {                                         \\r
101     n = hint##s + i*clkdiv/factor;                                      \\r
102     if (n >= gapstart##s) n += gapend##s-gapstart##s;                   \\r
103     hcounts_##s[i] = n % 256;                                           \\r
104   }                                                                     \\r
105                                                                         \\r
106   ax = bx = ac = bc = rx = 0;                                           \\r
107   for (i = 0; i < cycsz; i++) {                                         \\r
108     n = hcounts_##s[i];                                                 \\r
109     if (i == 0 || n != hcounts_##s[i-1]) {                              \\r
110       /* fill slt <=> cycle tables, active scanline */                  \\r
111       if (ax < ARRAY_SIZE(dmaslots##s) && dmaslots##s[ax] == n) {       \\r
112         vdpsl2cyc_##s##_ac[++ax]=i;                                     \\r
113         while (ac < i) vdpcyc2sl_##s##_ac[ac++] = ax-1;                 \\r
114       }                                                                 \\r
115       /* fill slt <=> cycle tables, scanline off */                     \\r
116       if (rx >= ARRAY_SIZE(refslots##s) || refslots##s[rx] != n) {      \\r
117         vdpsl2cyc_##s##_bl[++bx]=i;                                     \\r
118         while (bc < i) vdpcyc2sl_##s##_bl[bc++] = bx-1;                 \\r
119       } else                                                            \\r
120         ++rx;                                                           \\r
121     }                                                                   \\r
122   }                                                                     \\r
123   /* fill up cycle to slot mappings for last slot */                    \\r
124   while (ac < cycsz)                                                    \\r
125     vdpcyc2sl_##s##_ac[ac] = ARRAY_SIZE(dmaslots##s),           ac++;   \\r
126   while (bc < cycsz)                                                    \\r
127     vdpcyc2sl_##s##_bl[bc] = slots##s-ARRAY_SIZE(refslots##s),  bc++;   \\r
128                                                                         \\r
129   /* extend tables for 2nd scanline */                                  \\r
130   memcpy(hcounts_##s+cycsz, hcounts_##s, ARRAY_SIZE(hcounts_##s)-cycsz);\\r
131   i = ARRAY_SIZE(dmaslots##s);                                          \\r
132   while (ac < ARRAY_SIZE(vdpcyc2sl_##s##_ac))                           \\r
133     vdpcyc2sl_##s##_ac[ac] = vdpcyc2sl_##s##_ac[ac-cycsz]+i,    ac++;   \\r
134   while (ax < ARRAY_SIZE(vdpsl2cyc_##s##_ac)-1)                 ax++,   \\r
135     vdpsl2cyc_##s##_ac[ax] = vdpsl2cyc_##s##_ac[ax-i]+cycsz;            \\r
136   i = slots##s - ARRAY_SIZE(refslots##s);                               \\r
137   while (bc < ARRAY_SIZE(vdpcyc2sl_##s##_bl))                           \\r
138     vdpcyc2sl_##s##_bl[bc] = vdpcyc2sl_##s##_bl[bc-cycsz]+i,    bc++;   \\r
139   while (bx < ARRAY_SIZE(vdpsl2cyc_##s##_bl)-1)                 bx++,   \\r
140     vdpsl2cyc_##s##_bl[bx] = vdpsl2cyc_##s##_bl[bx-i]+cycsz;            \\r
141 }\r
142  \r
143 \r
144 // initialize VDP timing tables\r
145 void PicoVideoInit(void)\r
146 {\r
147   INITTABLES(32);\r
148   INITTABLES(40);\r
149 }\r
150 \r
151 \r
152 static int linedisabled;    // display disabled on this line\r
153 static int lineenabled;     // display enabled on this line\r
154 static int lineoffset;      // offset at which dis/enable took place\r
155 \r
156 u32 SATaddr, SATmask;       // VRAM addr of sprite attribute table\r
157 \r
158 int (*PicoDmaHook)(u32 source, int len, unsigned short **base, u32 *mask) = NULL;\r
159 \r
160 \r
161 /* VDP FIFO implementation\r
162  * \r
163  * fifo_slot: last slot executed in this scanline\r
164  * fifo_total: #total FIFO entries pending\r
165  * fifo_data: last values transferred through fifo\r
166  * fifo_queue: fifo transfer queue (#writes, flags)\r
167  *\r
168  * FIFO states:         empty   total=0\r
169  *                      inuse   total>0 && total<4\r
170  *                      full    total==4\r
171  *                      wait    total>4\r
172  * Conditions:\r
173  * fifo_slot is normally behind slot2cyc[cycles]. Advancing it beyond cycles\r
174  * implies blocking the 68k up to that slot.\r
175  *\r
176  * A FIFO write goes to the end of the FIFO queue, but DMA running in background\r
177  * is always the last queue entry (transfers by CPU intervene and come 1st).\r
178  * There can be more pending writes than FIFO slots, but the CPU will be blocked\r
179  * until FIFO level (without background DMA) <= 4.\r
180  * This is only about correct timing, data xfer must be handled by the caller.\r
181  * Blocking the CPU means burning cycles via SekCyclesBurn*(), which is to be\r
182  * executed by the caller.\r
183  *\r
184  * FIFOSync "executes" FIFO write slots up to the given cycle in the current\r
185  * scanline. A queue entry completely executed is removed from the queue.\r
186  * FIFOWrite pushes writes to the transfer queue. If it's a blocking write, 68k\r
187  * is blocked if more than 4 FIFO writes are pending.\r
188  * FIFORead executes a 68k read. 68k is blocked until the next transfer slot.\r
189  */\r
190 \r
191 // NB code assumes fifo_* arrays have size 2^n\r
192 static struct VdpFIFO {\r
193   // last transferred FIFO data, ...x = index  XXX currently only CPU\r
194   u16 fifo_data[4], fifo_dx;\r
195 \r
196   // queued FIFO transfers, ...x = index, ...l = queue length\r
197   // each entry has 2 values: [n]>>3 = #writes, [n]&7 = flags (FQ_*)\r
198   u32 fifo_queue[8], fifo_qx, fifo_ql;\r
199   int fifo_total;             // total# of pending FIFO entries (w/o BGDMA)\r
200 \r
201   unsigned short fifo_slot;   // last executed slot in current scanline\r
202   unsigned short fifo_maxslot;// #slots in scanline\r
203 \r
204   const unsigned short *fifo_cyc2sl;\r
205   const unsigned short *fifo_sl2cyc;\r
206   const unsigned char  *fifo_hcounts;\r
207 } VdpFIFO;\r
208 \r
209 enum { FQ_BYTE = 1, FQ_BGDMA = 2, FQ_FGDMA = 4 }; // queue flags, NB: BYTE = 1!\r
210 \r
211 \r
212 // NB should limit cyc2sl to table size in case 68k overdraws its aim. That can\r
213 // happen if the last op is a blocking acess to VDP, or for exceptions (e.g.irq)\r
214 #define Cyc2Sl(vf,lc)   ((vf)->fifo_cyc2sl[(lc)/clkdiv])\r
215 #define Sl2Cyc(vf,sl)   ((vf)->fifo_sl2cyc[sl]*clkdiv)\r
216 \r
217 // do the FIFO math\r
218 static int AdvanceFIFOEntry(struct VdpFIFO *vf, struct PicoVideo *pv, int slots)\r
219 {\r
220   u32 *qx = &vf->fifo_queue[vf->fifo_qx];\r
221   int l = slots, b = *qx & FQ_BYTE;\r
222   int cnt = *qx >> 3;\r
223 \r
224   // advance currently active FIFO entry\r
225   if (l > cnt)\r
226     l = cnt;\r
227   if (!(*qx & FQ_BGDMA))\r
228     vf->fifo_total -= ((cnt & b) + l) >> b;\r
229   *qx -= l << 3;\r
230 \r
231   // if entry has been processed...\r
232   if (cnt == l) {\r
233     // remove entry from FIFO\r
234     *qx = 0;\r
235     vf->fifo_qx = (vf->fifo_qx+1) & 7;\r
236     vf->fifo_ql --;\r
237   }\r
238 \r
239   return l;\r
240 }\r
241 \r
242 static void SetFIFOState(struct VdpFIFO *vf, struct PicoVideo *pv)\r
243 {\r
244   u32 st = pv->status, cmd = pv->command;\r
245   // release CPU and terminate DMA if FIFO isn't blocking the 68k anymore\r
246   if (vf->fifo_total <= 4) {\r
247     st &= ~PVS_CPUWR;\r
248     if (!(st & (PVS_DMABG|PVS_DMAFILL))) {\r
249       st &= ~SR_DMA;\r
250       cmd &= ~0x80;\r
251     }\r
252   }\r
253   if (vf->fifo_ql == 0) {\r
254     st &= ~PVS_CPURD;\r
255     // terminate DMA if applicable\r
256     if (!(st & PVS_DMAFILL)) {\r
257       st &= ~(SR_DMA|PVS_DMABG);\r
258       cmd &= ~0x80;\r
259     }\r
260   }\r
261   pv->status = st;\r
262   pv->command = cmd;\r
263 }\r
264 \r
265 // sync FIFO to cycles\r
266 void PicoVideoFIFOSync(int cycles)\r
267 {\r
268   struct VdpFIFO *vf = &VdpFIFO;\r
269   struct PicoVideo *pv = &Pico.video;\r
270   int slots, done;\r
271 \r
272   // calculate #slots since last executed slot\r
273   slots = Cyc2Sl(vf, cycles) - vf->fifo_slot;\r
274   if (slots <= 0 || !vf->fifo_ql) return;\r
275 \r
276   // advance FIFO queue by #done slots\r
277   done = slots;\r
278   while (done > 0 && vf->fifo_ql) {\r
279     int l = AdvanceFIFOEntry(vf, pv, done);\r
280     vf->fifo_slot += l;\r
281     done -= l;\r
282   }\r
283 \r
284   if (done != slots)\r
285     SetFIFOState(vf, pv);\r
286 }\r
287 \r
288 // drain FIFO, blocking 68k on the way. FIFO must be synced prior to drain.\r
289 static int PicoVideoFIFODrain(int level, int cycles, int bgdma)\r
290 {\r
291   struct VdpFIFO *vf = &VdpFIFO;\r
292   struct PicoVideo *pv = &Pico.video;\r
293   unsigned ocyc = cycles;\r
294   int bd = vf->fifo_queue[vf->fifo_qx] & bgdma;\r
295   int burn = 0;\r
296 \r
297   if (!(vf->fifo_ql && ((vf->fifo_total > level) | bd))) return 0;\r
298 \r
299   // process FIFO entries until low level is reached\r
300   while (vf->fifo_slot < vf->fifo_maxslot &&\r
301          vf->fifo_ql && ((vf->fifo_total > level) | bd)) {\r
302     int b = vf->fifo_queue[vf->fifo_qx] & FQ_BYTE;\r
303     int c = vf->fifo_queue[vf->fifo_qx] >> 3;\r
304     int cnt = bd ? c : ((vf->fifo_total-level)<<b) - (c&b);\r
305     int slot = (c < cnt ? c : cnt) + vf->fifo_slot;\r
306 \r
307     if (slot > vf->fifo_maxslot) {\r
308       // target slot in later scanline, advance to eol\r
309       slot = vf->fifo_maxslot;\r
310     }\r
311     if (slot > vf->fifo_slot) {\r
312       // advance FIFO to target slot and CPU to cycles at that slot\r
313       vf->fifo_slot += AdvanceFIFOEntry(vf, pv, slot - vf->fifo_slot);\r
314       cycles = Sl2Cyc(vf, vf->fifo_slot);\r
315       bd = vf->fifo_queue[vf->fifo_qx] & bgdma;\r
316     }\r
317   }\r
318   if (vf->fifo_ql && ((vf->fifo_total > level) | bd))\r
319     cycles = slcpu; // not completed in this scanline\r
320   if (cycles > ocyc)\r
321     burn = cycles - ocyc;\r
322 \r
323   SetFIFOState(vf, pv);\r
324 \r
325   return burn;\r
326 }\r
327 \r
328 // read VDP data port\r
329 static int PicoVideoFIFORead(void)\r
330 {\r
331   struct VdpFIFO *vf = &VdpFIFO;\r
332   struct PicoVideo *pv = &Pico.video;\r
333   int lc = SekCyclesDone()-Pico.t.m68c_line_start;\r
334   int burn = 0;\r
335 \r
336   if (vf->fifo_ql) {\r
337     // advance FIFO and CPU until FIFO is empty\r
338     burn = PicoVideoFIFODrain(0, lc, FQ_BGDMA);\r
339     lc += burn;\r
340   }\r
341 \r
342   if (vf->fifo_ql)\r
343     pv->status |= PVS_CPURD; // target slot is in later scanline\r
344   else {\r
345     // use next VDP access slot for reading, block 68k until then\r
346     vf->fifo_slot = Cyc2Sl(vf, lc) + 1;\r
347     burn += Sl2Cyc(vf, vf->fifo_slot) - lc;\r
348   }\r
349 \r
350   return burn;\r
351 }\r
352  \r
353 // write VDP data port\r
354 int PicoVideoFIFOWrite(int count, int flags, unsigned sr_mask,unsigned sr_flags)\r
355 {\r
356   struct VdpFIFO *vf = &VdpFIFO;\r
357   struct PicoVideo *pv = &Pico.video;\r
358   int lc = SekCyclesDone()-Pico.t.m68c_line_start;\r
359   int burn = 0, x;\r
360 \r
361   // sync only needed if queue is too full or background dma might be deferred\r
362   if ((vf->fifo_ql >= 6) | (pv->status & PVS_DMABG))\r
363     PicoVideoFIFOSync(lc);\r
364 \r
365   // determine last ent, ignoring bg dma (pushed back below if new ent created)\r
366   x = (vf->fifo_qx + vf->fifo_ql - 1 - !!(pv->status & PVS_DMABG)) & 7;\r
367 \r
368   pv->status = (pv->status & ~sr_mask) | sr_flags;\r
369   vf->fifo_total += count * !(flags & FQ_BGDMA);\r
370   if (!vf->fifo_ql)\r
371     vf->fifo_slot = Cyc2Sl(vf, lc+7); // FIFO latency ~3 vdp slots\r
372 \r
373   // determine queue position for entry\r
374   count <<= (flags & FQ_BYTE)+3;\r
375   if (vf->fifo_queue[x] && (vf->fifo_queue[x] & 7) == flags) {\r
376     // amalgamate entries if of same type and not empty (in case of bgdma)\r
377     vf->fifo_queue[x] += count;\r
378   } else {\r
379     // create new xfer queue entry\r
380     vf->fifo_ql ++;\r
381     x = (x+1) & 7;\r
382     vf->fifo_queue[(x+1)&7] = vf->fifo_queue[x]; // push back bg dma if exists\r
383     vf->fifo_queue[x] = count | flags;\r
384   }\r
385 \r
386   // if CPU is waiting for the bus, advance CPU and FIFO until bus is free\r
387   // do this only if it would exhaust the available slots since last sync\r
388   x = (Cyc2Sl(vf,lc) - vf->fifo_slot) / 2; // lower bound of FIFO ents \r
389   if ((pv->status & PVS_CPUWR) && vf->fifo_total > 4 + x)\r
390     burn = PicoVideoFIFODrain(4, lc, 0);\r
391 \r
392   return burn;\r
393 }\r
394 \r
395 // at HINT, advance FIFO to new scanline\r
396 int PicoVideoFIFOHint(void)\r
397 {\r
398   struct VdpFIFO *vf = &VdpFIFO;\r
399   struct PicoVideo *pv = &Pico.video;\r
400   int lc = SekCyclesDone()-Pico.t.m68c_line_start;\r
401   int burn = 0;\r
402 \r
403   // reset slot to start of scanline\r
404   vf->fifo_slot = 0;\r
405   // only need to refresh sprite position if we are synced\r
406   if (Pico.est.DrawScanline == Pico.m.scanline && !(pv->status & SR_VB))\r
407     PicoDrawRefreshSprites();\r
408  \r
409   // if CPU is waiting for the bus, advance CPU and FIFO until bus is free\r
410   if (pv->status & PVS_CPUWR)\r
411     burn = PicoVideoFIFODrain(4, lc, 0);\r
412   else if (pv->status & PVS_CPURD)\r
413     burn = PicoVideoFIFORead();\r
414 \r
415   return burn;\r
416 }\r
417 \r
418 // switch FIFO mode between active/inactive display\r
419 void PicoVideoFIFOMode(int active, int h40)\r
420 {\r
421   static const unsigned short *vdpcyc2sl[2][2] =\r
422       { {vdpcyc2sl_32_bl, vdpcyc2sl_40_bl},{vdpcyc2sl_32_ac, vdpcyc2sl_40_ac} };\r
423   static const unsigned short *vdpsl2cyc[2][2] =\r
424       { {vdpsl2cyc_32_bl, vdpsl2cyc_40_bl},{vdpsl2cyc_32_ac, vdpsl2cyc_40_ac} };\r
425   static const unsigned char *vdphcounts[2] =\r
426       { hcounts_32, hcounts_40 };\r
427 \r
428   struct VdpFIFO *vf = &VdpFIFO;\r
429   struct PicoVideo *pv = &Pico.video;\r
430   int lc = SekCyclesDone() - Pico.t.m68c_line_start;\r
431   active = active && !(pv->status & PVS_VB2);\r
432 \r
433   if (vf->fifo_maxslot)\r
434     PicoVideoFIFOSync(lc);\r
435   else\r
436     lc = 0;\r
437 \r
438   vf->fifo_cyc2sl = vdpcyc2sl[active][h40];\r
439   vf->fifo_sl2cyc = vdpsl2cyc[active][h40];\r
440   vf->fifo_hcounts = vdphcounts[h40];\r
441   // recalculate FIFO slot for new mode\r
442   vf->fifo_slot = Cyc2Sl(vf, lc);\r
443   vf->fifo_maxslot = Cyc2Sl(vf, slcpu);\r
444 }\r
445 \r
446 // VDP memory rd/wr\r
447 \r
448 static __inline void AutoIncrement(void)\r
449 {\r
450   struct PicoVideo *pvid = &Pico.video;\r
451   pvid->addr=(unsigned short)(pvid->addr+pvid->reg[0xf]);\r
452   if (pvid->addr < pvid->reg[0xf]) pvid->addr_u ^= 1;\r
453 }\r
454 \r
455 static NOINLINE void VideoWriteVRAM128(u32 a, u16 d)\r
456 {\r
457   // nasty\r
458   u32 b = ((a & 2) >> 1) | ((a & 0x400) >> 9) | (a & 0x3FC) | ((a & 0x1F800) >> 1);\r
459 \r
460   ((u8 *)PicoMem.vram)[b] = d;\r
461   if (!(u16)((b^SATaddr) & SATmask))\r
462     Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
463 \r
464   if (((a^SATaddr) & SATmask) == 0)\r
465     UpdateSAT(a, d);\r
466 }\r
467 \r
468 static void VideoWrite(u16 d)\r
469 {\r
470   struct PicoVideo *pvid = &Pico.video;\r
471   unsigned int a = pvid->addr;\r
472 \r
473   switch (pvid->type)\r
474   {\r
475     case 1: if (a & 1)\r
476               d = (u16)((d << 8) | (d >> 8));\r
477             a |= pvid->addr_u << 16;\r
478             VideoWriteVRAM(a, d);\r
479             break;\r
480     case 3: if (PicoMem.cram [(a >> 1) & 0x3f] != (d & 0xeee)) Pico.m.dirtyPal = 1;\r
481             PicoMem.cram [(a >> 1) & 0x3f] = d & 0xeee; break;\r
482     case 5: PicoMem.vsram[(a >> 1) & 0x3f] = d & 0x7ff; break;\r
483     case 0x81:\r
484             a |= pvid->addr_u << 16;\r
485             VideoWriteVRAM128(a, d);\r
486             break;\r
487     //default:elprintf(EL_ANOMALY, "VDP write %04x with bad type %i", d, pvid->type); break;\r
488   }\r
489 \r
490   AutoIncrement();\r
491 }\r
492 \r
493 static unsigned int VideoRead(int is_from_z80)\r
494 {\r
495   struct PicoVideo *pvid = &Pico.video;\r
496   unsigned int a, d = VdpFIFO.fifo_data[(VdpFIFO.fifo_dx+1)&3];\r
497 \r
498   a=pvid->addr; a>>=1;\r
499 \r
500   if (!is_from_z80)\r
501     SekCyclesBurnRun(PicoVideoFIFORead());\r
502   switch (pvid->type)\r
503   {\r
504     case 0: d=PicoMem.vram [a & 0x7fff]; break;\r
505     case 8: d=PicoMem.cram [a & 0x003f] | (d & ~0x0eee); break;\r
506     case 4: if ((a & 0x3f) >= 0x28) a = 0;\r
507             d=PicoMem.vsram [a & 0x003f] | (d & ~0x07ff); break;\r
508     case 12:a=PicoMem.vram [a & 0x7fff]; if (pvid->addr&1) a >>= 8;\r
509             d=(a & 0x00ff) | (d & ~0x00ff); break;\r
510     default:elprintf(EL_ANOMALY, "VDP read with bad type %i", pvid->type); break;\r
511   }\r
512 \r
513   AutoIncrement();\r
514   return d;\r
515 }\r
516 \r
517 // VDP DMA\r
518 \r
519 static int GetDmaLength(void)\r
520 {\r
521   struct PicoVideo *pvid=&Pico.video;\r
522   int len=0;\r
523   // 16-bit words to transfer:\r
524   len =pvid->reg[0x13];\r
525   len|=pvid->reg[0x14]<<8;\r
526   len = ((len - 1) & 0xffff) + 1;\r
527   return len;\r
528 }\r
529 \r
530 static void DmaSlow(int len, u32 source)\r
531 {\r
532   struct PicoVideo *pvid=&Pico.video;\r
533   u32 inc = pvid->reg[0xf];\r
534   u32 a = pvid->addr | (pvid->addr_u << 16), e;\r
535   u16 *r, *base = NULL;\r
536   u32 mask = 0x1ffff;\r
537   int lc = SekCyclesDone()-Pico.t.m68c_line_start;\r
538 \r
539   elprintf(EL_VDPDMA, "DmaSlow[%i] %06x->%04x len %i inc=%i blank %i [%u] @ %06x",\r
540     pvid->type, source, a, len, inc, (pvid->status&SR_VB)||!(pvid->reg[1]&0x40),\r
541     SekCyclesDone(), SekPc);\r
542 \r
543   SekCyclesBurnRun(PicoVideoFIFOWrite(len, FQ_FGDMA | (pvid->type == 1),\r
544                               PVS_DMABG, SR_DMA | PVS_CPUWR));\r
545   // short transfers might have been completely conveyed to FIFO, adjust state\r
546   if ((pvid->status & SR_DMA) && VdpFIFO.fifo_total <= 4)\r
547     SetFIFOState(&VdpFIFO, pvid);\r
548 \r
549   if ((source & 0xe00000) == 0xe00000) { // Ram\r
550     base = (u16 *)PicoMem.ram;\r
551     mask = 0xffff;\r
552   }\r
553   else if (PicoIn.AHW & PAHW_MCD)\r
554   {\r
555     u8 r3 = Pico_mcd->s68k_regs[3];\r
556     elprintf(EL_VDPDMA, "DmaSlow CD, r3=%02x", r3);\r
557     if ((source & 0xfc0000) == pcd_base_address) { // Bios area\r
558       base = (u16 *)(Pico_mcd->bios + (source & 0xfe0000));\r
559     } else if ((source & 0xfc0000) == pcd_base_address+0x200000) { // Word Ram\r
560       if (!(r3 & 4)) { // 2M mode\r
561         base = (u16 *)(Pico_mcd->word_ram2M + (source & 0x20000));\r
562       } else {\r
563         if ((source & 0xfe0000) < pcd_base_address+0x220000) { // 1M mode\r
564           int bank = r3 & 1;\r
565           base = (u16 *)(Pico_mcd->word_ram1M[bank]);\r
566         } else {\r
567           DmaSlowCell(source - 2, a, len, inc);\r
568           return;\r
569         }\r
570       }\r
571       source -= 2;\r
572     } else if ((source & 0xfe0000) == pcd_base_address+0x020000) { // Prg Ram\r
573       base = (u16 *)Pico_mcd->prg_ram_b[r3 >> 6];\r
574       source -= 2; // XXX: test\r
575     } else // Rom\r
576       base = m68k_dma_source(source);\r
577   }\r
578   else\r
579   {\r
580     // if we have DmaHook, let it handle ROM because of possible DMA delay\r
581     u32 source2;\r
582     if (PicoDmaHook && (source2 = PicoDmaHook(source, len, &base, &mask)))\r
583       source = source2;\r
584     else // Rom\r
585       base = m68k_dma_source(source);\r
586   }\r
587   if (!base) {\r
588     elprintf(EL_VDPDMA|EL_ANOMALY, "DmaSlow[%i] %06x->%04x: invalid src", pvid->type, source, a);\r
589     return;\r
590   }\r
591 \r
592   // operate in words\r
593   source >>= 1;\r
594   mask >>= 1;\r
595 \r
596   switch (pvid->type)\r
597   {\r
598     case 1: // vram\r
599       e = a + len*2-1;\r
600       r = PicoMem.vram;\r
601       if (inc == 2 && !(a & 1) && !((a ^ e) >> 16) &&\r
602           ((a >= SATaddr + 0x280) | (e < SATaddr)) &&\r
603           !((source ^ (source + len-1)) & ~mask))\r
604       {\r
605         // most used DMA mode\r
606         memcpy((char *)r + a, base + (source & mask), len * 2);\r
607         a += len * 2;\r
608         break;\r
609       }\r
610       for(; len; len--)\r
611       {\r
612         u16 d = base[source++ & mask];\r
613         if(a & 1) d=(d<<8)|(d>>8);\r
614         VideoWriteVRAM(a, d);\r
615         // AutoIncrement\r
616         a = (a+inc) & ~0x20000;\r
617       }\r
618       break;\r
619 \r
620     case 3: // cram\r
621       Pico.m.dirtyPal = 1;\r
622       r = PicoMem.cram;\r
623       if (inc == 0 && !(pvid->reg[1] & 0x40) &&\r
624             (pvid->reg[7] & 0x3f) == ((a/2) & 0x3f)) { // bg color DMA\r
625         PicoVideoSync(1);\r
626         int sl = VdpFIFO.fifo_hcounts[lc/clkdiv];\r
627         if (sl > VdpFIFO.fifo_hcounts[0]-5) // hint delay is 5 slots\r
628           sl = (s8)sl;\r
629         // TODO this is needed to cover timing inaccuracies\r
630         if (sl <= 12) sl = -3;\r
631         else if (sl <= 40) sl = 30;\r
632         PicoDrawBgcDMA(base, source, mask, len, sl);\r
633         // do last DMA cycle since it's all going to the same cram location\r
634         source = source+len-1;\r
635         len = 1;\r
636       }\r
637       for (; len; len--)\r
638       {\r
639         r[(a / 2) & 0x3f] = base[source++ & mask] & 0xeee;\r
640         // AutoIncrement\r
641         a = (a+inc) & ~0x20000;\r
642       }\r
643       break;\r
644 \r
645     case 5: // vsram\r
646       r = PicoMem.vsram;\r
647       for (; len; len--)\r
648       {\r
649         r[(a / 2) & 0x3f] = base[source++ & mask] & 0x7ff;\r
650         // AutoIncrement\r
651         a = (a+inc) & ~0x20000;\r
652       }\r
653       break;\r
654 \r
655     case 0x81: // vram 128k\r
656       for(; len; len--)\r
657       {\r
658         u16 d = base[source++ & mask];\r
659         VideoWriteVRAM128(a, d);\r
660         // AutoIncrement\r
661         a = (a+inc) & ~0x20000;\r
662       }\r
663       break;\r
664 \r
665     default:\r
666       if (pvid->type != 0 || (EL_LOGMASK & EL_VDPDMA))\r
667         elprintf(EL_VDPDMA|EL_ANOMALY, "DMA with bad type %i", pvid->type);\r
668       break;\r
669   }\r
670   // remember addr\r
671   pvid->addr = a;\r
672   pvid->addr_u = a >> 16;\r
673 }\r
674 \r
675 static void DmaCopy(int len)\r
676 {\r
677   struct PicoVideo *pvid=&Pico.video;\r
678   u32 a = pvid->addr | (pvid->addr_u << 16);\r
679   u8 *vr = (u8 *)PicoMem.vram;\r
680   u8 inc = pvid->reg[0xf];\r
681   int source;\r
682   elprintf(EL_VDPDMA, "DmaCopy len %i [%u]", len, SekCyclesDone());\r
683 \r
684   // XXX implement VRAM 128k? Is this even working? xfer/count still in bytes?\r
685   SekCyclesBurnRun(PicoVideoFIFOWrite(2*len, FQ_BGDMA, // 2 slots each (rd+wr)\r
686                               PVS_CPUWR, SR_DMA | PVS_DMABG));\r
687 \r
688   source =pvid->reg[0x15];\r
689   source|=pvid->reg[0x16]<<8;\r
690 \r
691   for (; len; len--)\r
692   {\r
693     vr[(u16)a] = vr[(u16)(source++)];\r
694     if (((a^SATaddr) & SATmask) == 0)\r
695       UpdateSAT(a, ((u16 *)vr)[(u16)a >> 1]);\r
696     // AutoIncrement\r
697     a = (a+inc) & ~0x20000;\r
698   }\r
699   // remember addr\r
700   pvid->addr = a;\r
701   pvid->addr_u = a >> 16;\r
702 }\r
703 \r
704 static NOINLINE void DmaFill(int data)\r
705 {\r
706   struct PicoVideo *pvid=&Pico.video;\r
707   u32 a = pvid->addr | (pvid->addr_u << 16), e;\r
708   u8 *vr = (u8 *)PicoMem.vram;\r
709   u8 high = (u8)(data >> 8);\r
710   u8 inc = pvid->reg[0xf];\r
711   int source;\r
712   int len, l;\r
713 \r
714   len = GetDmaLength();\r
715   elprintf(EL_VDPDMA, "DmaFill len %i inc %i [%u]", len, inc, SekCyclesDone());\r
716 \r
717   SekCyclesBurnRun(PicoVideoFIFOWrite(len, FQ_BGDMA, // 1 slot each (wr)\r
718                               PVS_CPUWR | PVS_DMAFILL, SR_DMA | PVS_DMABG));\r
719 \r
720   switch (pvid->type)\r
721   {\r
722     case 1: // vram\r
723       e = a + len-1;\r
724       if (inc == 1 && !((a ^ e) >> 16) &&\r
725           ((a >= SATaddr + 0x280) | (e < SATaddr)))\r
726       {\r
727         // most used DMA mode\r
728         memset(vr + (u16)a, high, len);\r
729         a += len;\r
730         break;\r
731       }\r
732       for (l = len; l; l--) {\r
733         // Write upper byte to adjacent address\r
734         // (here we are byteswapped, so address is already 'adjacent')\r
735         vr[(u16)a] = high;\r
736         if (((a^SATaddr) & SATmask) == 0)\r
737           UpdateSAT(a, ((u16 *)vr)[(u16)a >> 1]);\r
738 \r
739         // Increment address register\r
740         a = (a+inc) & ~0x20000;\r
741       }\r
742       break;\r
743     case 3:   // cram\r
744       Pico.m.dirtyPal = 1;\r
745       data &= 0xeee;\r
746       for (l = len; l; l--) {\r
747         PicoMem.cram[(a/2) & 0x3f] = data;\r
748 \r
749         // Increment address register\r
750         a = (a+inc) & ~0x20000;\r
751       }\r
752       break;\r
753     case 5: { // vsram\r
754       data &= 0x7ff;\r
755       for (l = len; l; l--) {\r
756         PicoMem.vsram[(a/2) & 0x3f] = data;\r
757 \r
758         // Increment address register\r
759         a = (a+inc) & ~0x20000;\r
760       }\r
761       break;\r
762     }\r
763     case 0x81: // vram 128k\r
764       for (l = len; l; l--) {\r
765         VideoWriteVRAM128(a, data);\r
766 \r
767         // Increment address register\r
768         a = (a+inc) & ~0x20000;\r
769       }\r
770       break;\r
771     default:\r
772       a += len * inc;\r
773       break;\r
774   }\r
775 \r
776   // remember addr\r
777   pvid->addr = a;\r
778   pvid->addr_u = a >> 16;\r
779   // register update\r
780   pvid->reg[0x13] = pvid->reg[0x14] = 0;\r
781   source  = pvid->reg[0x15];\r
782   source |= pvid->reg[0x16] << 8;\r
783   source += len;\r
784   pvid->reg[0x15] = source;\r
785   pvid->reg[0x16] = source >> 8;\r
786 }\r
787 \r
788 // VDP command handling\r
789 \r
790 static NOINLINE void CommandDma(void)\r
791 {\r
792   struct PicoVideo *pvid = &Pico.video;\r
793   u32 len, method;\r
794   u32 source;\r
795 \r
796   PicoVideoFIFOSync(SekCyclesDone()-Pico.t.m68c_line_start);\r
797   if (pvid->status & SR_DMA) {\r
798     elprintf(EL_VDPDMA, "Dma overlap, left=%d @ %06x",\r
799              VdpFIFO.fifo_total, SekPc);\r
800     VdpFIFO.fifo_total = VdpFIFO.fifo_ql = 0;\r
801     pvid->status &= ~PVS_DMAFILL;\r
802   }\r
803 \r
804   len = GetDmaLength();\r
805   source  = pvid->reg[0x15];\r
806   source |= pvid->reg[0x16] << 8;\r
807   source |= pvid->reg[0x17] << 16;\r
808 \r
809   method=pvid->reg[0x17]>>6;\r
810   if (method < 2)\r
811     DmaSlow(len, source << 1); // 68000 to VDP\r
812   else if (method == 3)\r
813     DmaCopy(len); // VRAM Copy\r
814   else {\r
815     pvid->status |= SR_DMA|PVS_DMAFILL;\r
816     return;\r
817   }\r
818   source += len;\r
819   pvid->reg[0x13] = pvid->reg[0x14] = 0;\r
820   pvid->reg[0x15] = source;\r
821   pvid->reg[0x16] = source >> 8;\r
822 }\r
823 \r
824 static NOINLINE void CommandChange(struct PicoVideo *pvid)\r
825 {\r
826   unsigned int cmd, addr;\r
827 \r
828   cmd = pvid->command;\r
829 \r
830   // Get type of transfer 0xc0000030 (v/c/vsram read/write)\r
831   pvid->type = (u8)(((cmd >> 2) & 0xc) | (cmd >> 30));\r
832   if (pvid->type == 1) // vram\r
833     pvid->type |= pvid->reg[1] & 0x80; // 128k\r
834 \r
835   // Get address 0x3fff0003\r
836   addr  = (cmd >> 16) & 0x3fff;\r
837   addr |= (cmd << 14) & 0xc000;\r
838   pvid->addr = (u16)addr;\r
839   pvid->addr_u = (u8)((cmd >> 2) & 1);\r
840 }\r
841 \r
842 // VDP interface\r
843  \r
844 static inline int InHblank(int offs)\r
845 {\r
846   // check if in left border (14 pixels) or HBLANK (86 pixels), 116 68k cycles\r
847   return SekCyclesDone() - Pico.t.m68c_line_start <= offs;\r
848 }\r
849 \r
850 void PicoVideoSync(int skip)\r
851 {\r
852   struct VdpFIFO *vf = &VdpFIFO;\r
853   int lines = Pico.video.reg[1]&0x08 ? 240 : 224;\r
854   int last = Pico.m.scanline - (skip > 0);\r
855 \r
856   if (!(PicoIn.opt & POPT_ALT_RENDERER) && !PicoIn.skipFrame) {\r
857     if (last >= lines)\r
858       last = lines-1;\r
859     else // in active display, need to sync next frame as well\r
860       Pico.est.rendstatus |= PDRAW_SYNC_NEXT;\r
861 \r
862     //elprintf(EL_ANOMALY, "sync");\r
863     if (unlikely(linedisabled >= 0 && linedisabled <= last)) {\r
864       if (Pico.est.DrawScanline <= linedisabled) {\r
865         int sl = vf->fifo_hcounts[lineoffset/clkdiv];\r
866         PicoDrawSync(linedisabled, sl ? sl : 1, 0);\r
867       }\r
868       linedisabled = -1;\r
869     }\r
870     if (unlikely(lineenabled >= 0 && lineenabled <= last)) {\r
871       if (Pico.est.DrawScanline <= lineenabled) {\r
872         int sl = vf->fifo_hcounts[lineoffset/clkdiv];\r
873         PicoDrawSync(lineenabled, 0, sl ? sl : 1);\r
874       }\r
875       lineenabled = -1;\r
876     }\r
877     if (Pico.est.DrawScanline <= last)\r
878       PicoDrawSync(last, 0, 0);\r
879   }\r
880   if (skip >= 0)\r
881     Pico.est.rendstatus |= PDRAW_SYNC_NEEDED;\r
882 }\r
883 \r
884 PICO_INTERNAL_ASM void PicoVideoWrite(u32 a,unsigned short d)\r
885 {\r
886   struct PicoVideo *pvid=&Pico.video;\r
887 \r
888   //elprintf(EL_STATUS, "PicoVideoWrite [%06x] %04x [%u] @ %06x",\r
889   //  a, d, SekCyclesDone(), SekPc);\r
890 \r
891   a &= 0x1c;\r
892   switch (a)\r
893   {\r
894   case 0x00: // Data port 0 or 2\r
895     if (pvid->pending) {\r
896       CommandChange(pvid);\r
897       pvid->pending=0;\r
898     }\r
899 \r
900     // try avoiding the sync if the data doesn't change.\r
901     // Writes to the SAT in VRAM are special since they update the SAT cache.\r
902     if ((pvid->reg[1]&0x40) &&\r
903         !(pvid->type == 1 && !(pvid->addr&1) && ((pvid->addr^SATaddr)&SATmask) && PicoMem.vram[pvid->addr>>1] == d) &&\r
904         !(pvid->type == 3 && PicoMem.cram[(pvid->addr>>1) & 0x3f] == (d & 0xeee)) &&\r
905         !(pvid->type == 5 && PicoMem.vsram[(pvid->addr>>1) & 0x3f] == (d & 0x7ff)))\r
906       // the vertical scroll value for this line must be read from VSRAM early,\r
907       // since the A/B tile row to be read depends on it. E.g. Skitchin, OD2\r
908       // in contrast, CRAM writes would have an immediate effect on the current\r
909       // pixel, so sync can be closer to start of actual image data\r
910       PicoVideoSync(InHblank(pvid->type == 3 ? 103 : 30)); // cram in Toy Story\r
911 \r
912     if (!(PicoIn.opt&POPT_DIS_VDP_FIFO))\r
913     {\r
914       VdpFIFO.fifo_data[++VdpFIFO.fifo_dx&3] = d;\r
915       SekCyclesBurnRun(PicoVideoFIFOWrite(1, pvid->type == 1, 0, PVS_CPUWR));\r
916 \r
917       elprintf(EL_ASVDP, "VDP data write: [%04x] %04x [%u] {%i} @ %06x",\r
918         pvid->addr, d, SekCyclesDone(), pvid->type, SekPc);\r
919     }\r
920     VideoWrite(d);\r
921 \r
922     // start DMA fill on write. NB VSRAM and CRAM fills use wrong FIFO data.\r
923     if (pvid->status & PVS_DMAFILL)\r
924       DmaFill(VdpFIFO.fifo_data[(VdpFIFO.fifo_dx + !!(pvid->type&~0x81))&3]);\r
925 \r
926     break;\r
927 \r
928   case 0x04: // Control (command) port 4 or 6\r
929     if (pvid->status & SR_DMA)\r
930       SekCyclesBurnRun(PicoVideoFIFORead()); // kludge, flush out running DMA\r
931     if (pvid->pending)\r
932     {\r
933       // Low word of command:\r
934       if (!(pvid->reg[1]&0x10))\r
935         d = (d&~0x80)|(pvid->command&0x80);\r
936       pvid->command &= 0xffff0000;\r
937       pvid->command |= d;\r
938       pvid->pending = 0;\r
939       CommandChange(pvid);\r
940       // Check for dma:\r
941       if (d & 0x80) {\r
942         PicoVideoSync(InHblank(93));\r
943         CommandDma();\r
944       }\r
945     }\r
946     else\r
947     {\r
948       if ((d&0xc000)==0x8000)\r
949       {\r
950         // Register write:\r
951         int num=(d>>8)&0x1f;\r
952         int dold=pvid->reg[num];\r
953         pvid->type=0; // register writes clear command (else no Sega logo in Golden Axe II)\r
954         if (num > 0x0a && !(pvid->reg[1]&4)) {\r
955           elprintf(EL_ANOMALY, "%02x written to reg %02x in SMS mode @ %06x", d, num, SekPc);\r
956           return;\r
957         }\r
958 \r
959         d &= 0xff;\r
960 \r
961         if (num == 1 && ((pvid->reg[1]^d)&0x40)) {\r
962           // handle line blanking before line rendering. Only the last switch\r
963           // before the 1st sync for other reasons is honoured. Switching after\r
964           // active area is on next line\r
965           int skip = InHblank(470); // Deadly Moves\r
966           PicoVideoSync(skip);\r
967           lineenabled = (d&0x40) ? Pico.m.scanline + !skip: -1;\r
968           linedisabled = (d&0x40) ? -1 : Pico.m.scanline + !skip;\r
969           lineoffset = (skip ? SekCyclesDone() - Pico.t.m68c_line_start : 0);\r
970         } else if (((1<<num) & 0x738ff) && pvid->reg[num] != d)\r
971           // VDP regs 0-7,11-13,16-18 influence rendering, ignore all others\r
972           PicoVideoSync(InHblank(93)); // Toy Story\r
973         pvid->reg[num] = d;\r
974 \r
975         switch (num)\r
976         {\r
977           case 0x00:\r
978             if ((~dold&d)&2) {\r
979               unsigned c = SekCyclesDone() - Pico.t.m68c_line_start;\r
980               pvid->hv_latch = VdpFIFO.fifo_hcounts[c/clkdiv] | (pvid->v_counter << 8);\r
981             }\r
982             elprintf(EL_INTSW, "hint_onoff: %i->%i [%u] pend=%i @ %06x", (dold&0x10)>>4,\r
983                     (d&0x10)>>4, SekCyclesDone(), (pvid->pending_ints&0x10)>>4, SekPc);\r
984             goto update_irq;\r
985           case 0x01:\r
986             if ((d^dold)&0x40)\r
987               PicoVideoFIFOMode(d & 0x40, pvid->reg[12]&1);\r
988             if (!(pvid->status & PVS_VB2))\r
989               pvid->status &= ~SR_VB;\r
990             pvid->status |= ((d >> 3) ^ SR_VB) & SR_VB; // forced blanking\r
991             elprintf(EL_INTSW, "vint_onoff: %i->%i [%u] pend=%i @ %06x", (dold&0x20)>>5,\r
992                     (d&0x20)>>5, SekCyclesDone(), (pvid->pending_ints&0x20)>>5, SekPc);\r
993             goto update_irq;\r
994           case 0x05:\r
995           case 0x06:\r
996             if (d^dold) Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
997             break;\r
998           case 0x0c:\r
999             // renderers should update their palettes if sh/hi mode is changed\r
1000             if ((d^dold)&8) Pico.m.dirtyPal = 1;\r
1001             if ((d^dold)&1) {\r
1002               PicoVideoFIFOMode(pvid->reg[1]&0x40, d & 1);\r
1003               Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
1004             }\r
1005             break;\r
1006           default:\r
1007             return;\r
1008         }\r
1009         if (Pico.est.rendstatus & PDRAW_DIRTY_SPRITES) {\r
1010           SATaddr = ((pvid->reg[5]&0x7f) << 9) | ((pvid->reg[6]&0x20) << 11);\r
1011           SATmask = ~0x1ff;\r
1012           if (pvid->reg[12]&1)\r
1013             SATaddr &= ~0x200, SATmask &= ~0x200; // H40, zero lowest SAT bit\r
1014           //elprintf(EL_STATUS, "spritep moved to %04x", SATaddr);\r
1015         }\r
1016         return;\r
1017 \r
1018 update_irq:\r
1019 #ifndef EMU_CORE_DEBUG\r
1020         // update IRQ level; TODO hack, still fire irq if disabling now\r
1021         if (!SekShouldInterrupt() || SekIrqLevel < pvid->hint_irq)\r
1022         {\r
1023           int lines, pints, irq = 0;\r
1024           lines = (pvid->reg[1] & 0x20) | (pvid->reg[0] & 0x10);\r
1025           pints = pvid->pending_ints & lines;\r
1026                if (pints & 0x20) irq = 6;\r
1027           else if (pints & 0x10) irq = pvid->hint_irq;\r
1028 \r
1029           if (irq) {\r
1030             // VDP irqs have highest prio, just overwrite old level\r
1031             SekInterrupt(irq); // update line\r
1032 \r
1033             // TODO this is broken because cost of current insn isn't known here\r
1034             SekEndRun(21); // make it delayed\r
1035           } else if (SekIrqLevel >= pvid->hint_irq) {\r
1036             // no VDP irq, query lower irqs\r
1037             SekInterrupt(PicoIn.AHW & PAHW_PICO ? PicoPicoIrqAck(0) : 0);\r
1038           }\r
1039         }\r
1040 #endif\r
1041       }\r
1042       else\r
1043       {\r
1044         // High word of command:\r
1045         pvid->command&=0x0000ffff;\r
1046         pvid->command|=d<<16;\r
1047         pvid->pending=1;\r
1048       }\r
1049     }\r
1050     break;\r
1051 \r
1052   // case 0x08: // 08 0a - HV counter - lock up\r
1053   // case 0x0c: // 0c 0e - HV counter - lock up\r
1054   // case 0x10: // 10 12 - PSG - handled by caller\r
1055   // case 0x14: // 14 16 - PSG - handled by caller\r
1056   // case 0x18: // 18 1a - no effect?\r
1057   case 0x1c: // 1c 1e - debug\r
1058     pvid->debug = d;\r
1059     pvid->debug_p = 0;\r
1060     if (d & (1 << 6)) {\r
1061       pvid->debug_p |= PVD_KILL_A | PVD_KILL_B;\r
1062       pvid->debug_p |= PVD_KILL_S_LO | PVD_KILL_S_HI;\r
1063     }\r
1064     switch ((d >> 7) & 3) {\r
1065       case 1:\r
1066         pvid->debug_p &= ~(PVD_KILL_S_LO | PVD_KILL_S_HI);\r
1067         pvid->debug_p |= PVD_FORCE_S;\r
1068         break;\r
1069       case 2:\r
1070         pvid->debug_p &= ~PVD_KILL_A;\r
1071         pvid->debug_p |= PVD_FORCE_A;\r
1072         break;\r
1073       case 3:\r
1074         pvid->debug_p &= ~PVD_KILL_B;\r
1075         pvid->debug_p |= PVD_FORCE_B;\r
1076         break;\r
1077     }\r
1078     break;\r
1079   }\r
1080 }\r
1081 \r
1082 static u32 VideoSr(const struct PicoVideo *pv)\r
1083 {\r
1084   unsigned int hp = pv->reg[12]&1 ? hboff40*488.5/slots40 : hboff32*488.5/slots32;\r
1085   unsigned int hl = pv->reg[12]&1 ? hblen40*488.5/slots40 : hblen32*488.5/slots32;\r
1086   unsigned int c = SekCyclesDone() - Pico.t.m68c_line_start;\r
1087   u32 d;\r
1088 \r
1089   PicoVideoFIFOSync(c);\r
1090   d = (u16)pv->status;\r
1091 \r
1092   if (c - hp < hl)\r
1093     d |= SR_HB;\r
1094 \r
1095   if (VdpFIFO.fifo_total >= 4)\r
1096     d |= SR_FULL;\r
1097   else if (!VdpFIFO.fifo_total)\r
1098     d |= SR_EMPT;\r
1099   return d;\r
1100 }\r
1101 \r
1102 PICO_INTERNAL_ASM u32 PicoVideoRead(u32 a)\r
1103 {\r
1104   struct PicoVideo *pv = &Pico.video;\r
1105   a &= 0x1c;\r
1106 \r
1107   if (a == 0x04) // control port\r
1108   {\r
1109     u32 d = VideoSr(pv);\r
1110     if (pv->pending) {\r
1111       CommandChange(pv);\r
1112       pv->pending = 0;\r
1113     }\r
1114     elprintf(EL_SR, "SR read: %04x [%u] @ %06x", d, SekCyclesDone(), SekPc);\r
1115     return d;\r
1116   }\r
1117 \r
1118   if (a == 0x08)\r
1119   {\r
1120     unsigned int c;\r
1121     u32 d;\r
1122 \r
1123     c = SekCyclesDone() - Pico.t.m68c_line_start;\r
1124     if (pv->reg[0]&2)\r
1125          d = pv->hv_latch;\r
1126     else d = VdpFIFO.fifo_hcounts[c/clkdiv] | (pv->v_counter << 8);\r
1127 \r
1128     elprintf(EL_HVCNT, "hv: %02x %02x [%u] @ %06x", d, pv->v_counter, SekCyclesDone(), SekPc);\r
1129     return d;\r
1130   }\r
1131 \r
1132   if (a==0x00) // data port\r
1133   {\r
1134     return VideoRead(0);\r
1135   }\r
1136 \r
1137   return PicoRead16_floating(a | 0xc00000);\r
1138 }\r
1139 \r
1140 unsigned char PicoVideoRead8DataH(int is_from_z80)\r
1141 {\r
1142   return VideoRead(is_from_z80) >> 8;\r
1143 }\r
1144 \r
1145 unsigned char PicoVideoRead8DataL(int is_from_z80)\r
1146 {\r
1147   return VideoRead(is_from_z80);\r
1148 }\r
1149 \r
1150 unsigned char PicoVideoRead8CtlH(int is_from_z80)\r
1151 {\r
1152   struct PicoVideo *pv = &Pico.video;\r
1153   u8 d = VideoSr(pv) >> 8;\r
1154   if (pv->pending) {\r
1155     CommandChange(pv);\r
1156     pv->pending = 0;\r
1157   }\r
1158   elprintf(EL_SR, "SR read (h): %02x @ %06x", d, SekPc);\r
1159   return d;\r
1160 }\r
1161 \r
1162 unsigned char PicoVideoRead8CtlL(int is_from_z80)\r
1163 {\r
1164   struct PicoVideo *pv = &Pico.video;\r
1165   u8 d = VideoSr(pv);\r
1166   if (pv->pending) {\r
1167     CommandChange(pv);\r
1168     pv->pending = 0;\r
1169   }\r
1170   elprintf(EL_SR, "SR read (l): %02x @ %06x", d, SekPc);\r
1171   return d;\r
1172 }\r
1173 \r
1174 unsigned char PicoVideoRead8HV_H(int is_from_z80)\r
1175 {\r
1176   u32 d = Pico.video.v_counter;\r
1177   if (Pico.video.reg[0]&2)\r
1178     d = Pico.video.hv_latch >> 8;\r
1179   elprintf(EL_HVCNT, "vcounter: %02x [%u] @ %06x", d, SekCyclesDone(), SekPc);\r
1180   return d;\r
1181 }\r
1182 \r
1183 // FIXME: broken\r
1184 unsigned char PicoVideoRead8HV_L(int is_from_z80)\r
1185 {\r
1186   u32 d = SekCyclesDone() - Pico.t.m68c_line_start;\r
1187   if (Pico.video.reg[0]&2)\r
1188        d = Pico.video.hv_latch;\r
1189   else d = VdpFIFO.fifo_hcounts[d/clkdiv];\r
1190   elprintf(EL_HVCNT, "hcounter: %02x [%u] @ %06x", d, SekCyclesDone(), SekPc);\r
1191   return d;\r
1192 }\r
1193 \r
1194 void PicoVideoReset(void)\r
1195 {\r
1196   Pico.video.pending_ints=0;\r
1197   Pico.video.reg[1] &= ~0x40; // TODO verify display disabled after reset\r
1198   Pico.video.reg[10] = 0xff; // HINT is turned off after reset\r
1199   Pico.video.status = 0x3428 | Pico.m.pal; // 'always set' bits | vblank | collision | pal\r
1200 \r
1201   memset(&VdpFIFO, 0, sizeof(VdpFIFO));\r
1202   Pico.m.dirtyPal = 1;\r
1203 \r
1204   PicoDrawBgcDMA(NULL, 0, 0, 0, 0);\r
1205   PicoVideoFIFOMode(Pico.video.reg[1]&0x40, Pico.video.reg[12]&1);\r
1206 }\r
1207 \r
1208 void PicoVideoCacheSAT(int load)\r
1209 {\r
1210   struct PicoVideo *pv = &Pico.video;\r
1211   int l;\r
1212 \r
1213   SATaddr = ((pv->reg[5]&0x7f) << 9) | ((pv->reg[6]&0x20) << 11);\r
1214   SATmask = ~0x1ff;\r
1215   if (pv->reg[12]&1)\r
1216     SATaddr &= ~0x200, SATmask &= ~0x200; // H40, zero lowest SAT bit\r
1217 \r
1218   // rebuild SAT cache XXX wrong since cache and memory can differ\r
1219   for (l = 0; load && l < 2*80; l ++) {\r
1220     u16 addr = SATaddr + l*4;\r
1221     ((u16 *)VdpSATCache)[l*2    ] = PicoMem.vram[(addr>>1)    ];\r
1222     ((u16 *)VdpSATCache)[l*2 + 1] = PicoMem.vram[(addr>>1) + 1];\r
1223   }\r
1224 \r
1225   Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
1226 }\r
1227 \r
1228 #include <stddef.h>\r
1229 \r
1230 int PicoVideoSave(void *buf)\r
1231 {\r
1232   u8 *bp = buf;\r
1233   int i;\r
1234 \r
1235   // FIFO stuff\r
1236   memcpy(bp, &VdpFIFO, offsetof(struct VdpFIFO, fifo_slot));\r
1237   bp += offsetof(struct VdpFIFO, fifo_slot);\r
1238 \r
1239   // SAT Cache\r
1240   for (i = 0; i < 80; i++, bp += sizeof(u32))\r
1241     memcpy(bp, VdpSATCache+2*i, sizeof(u32));\r
1242 \r
1243   return bp - (u8 *)buf;\r
1244 }\r
1245 \r
1246 void PicoVideoLoad(void *buf, int len)\r
1247 {\r
1248   struct VdpFIFO *vf = &VdpFIFO;\r
1249   struct PicoVideo *pv = &Pico.video;\r
1250   int b = pv->type == 1;\r
1251 \r
1252   SATaddr = ((pv->reg[5]&0x7f) << 9) | ((pv->reg[6]&0x20) << 11);\r
1253   SATmask = ~0x1ff;\r
1254   if (pv->reg[12]&1)\r
1255     SATaddr &= ~0x200, SATmask &= ~0x200; // H40, zero lowest SAT bit\r
1256 \r
1257   if (len) {\r
1258     int i;\r
1259     if (len >= offsetof(struct VdpFIFO, fifo_slot))\r
1260       memcpy(&VdpFIFO, buf, offsetof(struct VdpFIFO, fifo_slot));\r
1261     for (i = 0; i < 80; i++)\r
1262       memcpy(VdpSATCache+2*i, buf + offsetof(struct VdpFIFO, fifo_slot) + 4*i, sizeof(u32));\r
1263     return;\r
1264   }\r
1265 \r
1266   // convert former dma_xfers (why was this in PicoMisc anyway?)\r
1267   if (Pico.m.dma_xfers) {\r
1268     pv->fifo_cnt = Pico.m.dma_xfers << b;\r
1269     Pico.m.dma_xfers = 0;\r
1270   }\r
1271 \r
1272   // fake entries in the FIFO if there are outstanding transfers\r
1273   vf->fifo_ql = vf->fifo_qx = vf->fifo_total = 0;\r
1274   if (pv->fifo_cnt) {\r
1275     int wc = pv->fifo_cnt;\r
1276     vf->fifo_total = (wc+b) >> b;\r
1277     vf->fifo_queue[vf->fifo_qx + vf->fifo_ql] = (wc << 3) | b | FQ_FGDMA;\r
1278     vf->fifo_ql ++;\r
1279     if (vf->fifo_total > 4 && !(pv->status & (PVS_CPUWR|PVS_CPURD)))\r
1280       pv->status |= PVS_CPUWR;\r
1281   }\r
1282   if (pv->fifo_bgcnt) {\r
1283     int wc = pv->fifo_bgcnt;\r
1284     if (!vf->fifo_ql)\r
1285       pv->status |= PVS_DMABG;\r
1286     vf->fifo_queue[vf->fifo_qx + vf->fifo_ql] = (wc << 3)     | FQ_BGDMA;\r
1287     vf->fifo_ql ++;\r
1288   }\r
1289   PicoVideoCacheSAT(1);\r
1290   vf->fifo_maxslot = 0;\r
1291 }\r
1292 // vim:shiftwidth=2:ts=2:expandtab\r