| 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 { // XXX this must go into save file!\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 < Pico.romsize /*0x20000*/) { // Bios area\r |
| 558 | base = (u16 *)(Pico.rom + (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 | }\r |
| 576 | }\r |
| 577 | else\r |
| 578 | {\r |
| 579 | // if we have DmaHook, let it handle ROM because of possible DMA delay\r |
| 580 | u32 source2;\r |
| 581 | if (PicoDmaHook && (source2 = PicoDmaHook(source, len, &base, &mask)))\r |
| 582 | source = source2;\r |
| 583 | else // Rom\r |
| 584 | base = m68k_dma_source(source);\r |
| 585 | }\r |
| 586 | if (!base) {\r |
| 587 | elprintf(EL_VDPDMA|EL_ANOMALY, "DmaSlow[%i] %06x->%04x: invalid src", pvid->type, source, a);\r |
| 588 | return;\r |
| 589 | }\r |
| 590 | \r |
| 591 | // operate in words\r |
| 592 | source >>= 1;\r |
| 593 | mask >>= 1;\r |
| 594 | \r |
| 595 | switch (pvid->type)\r |
| 596 | {\r |
| 597 | case 1: // vram\r |
| 598 | e = a + len*2-1;\r |
| 599 | r = PicoMem.vram;\r |
| 600 | if (inc == 2 && !(a & 1) && !((a ^ e) >> 16) &&\r |
| 601 | ((a >= SATaddr + 0x280) | (e < SATaddr)) &&\r |
| 602 | !((source ^ (source + len-1)) & ~mask))\r |
| 603 | {\r |
| 604 | // most used DMA mode\r |
| 605 | memcpy((char *)r + a, base + (source & mask), len * 2);\r |
| 606 | a += len * 2;\r |
| 607 | break;\r |
| 608 | }\r |
| 609 | for(; len; len--)\r |
| 610 | {\r |
| 611 | u16 d = base[source++ & mask];\r |
| 612 | if(a & 1) d=(d<<8)|(d>>8);\r |
| 613 | VideoWriteVRAM(a, d);\r |
| 614 | // AutoIncrement\r |
| 615 | a = (a+inc) & ~0x20000;\r |
| 616 | }\r |
| 617 | break;\r |
| 618 | \r |
| 619 | case 3: // cram\r |
| 620 | Pico.m.dirtyPal = 1;\r |
| 621 | r = PicoMem.cram;\r |
| 622 | if (inc == 0 && !(pvid->reg[1] & 0x40) &&\r |
| 623 | (pvid->reg[7] & 0x3f) == ((a/2) & 0x3f)) { // bg color DMA\r |
| 624 | PicoVideoSync(1);\r |
| 625 | int sl = VdpFIFO.fifo_hcounts[lc/clkdiv];\r |
| 626 | if (sl > VdpFIFO.fifo_hcounts[0]-5) // hint delay is 5 slots\r |
| 627 | sl = (s8)sl;\r |
| 628 | // TODO this is needed to cover timing inaccuracies\r |
| 629 | if (sl <= 12) sl = -3;\r |
| 630 | else if (sl <= 40) sl = 30;\r |
| 631 | PicoDrawBgcDMA(base, source, mask, len, sl);\r |
| 632 | // do last DMA cycle since it's all going to the same cram location\r |
| 633 | source = source+len-1;\r |
| 634 | len = 1;\r |
| 635 | }\r |
| 636 | for (; len; len--)\r |
| 637 | {\r |
| 638 | r[(a / 2) & 0x3f] = base[source++ & mask] & 0xeee;\r |
| 639 | // AutoIncrement\r |
| 640 | a = (a+inc) & ~0x20000;\r |
| 641 | }\r |
| 642 | break;\r |
| 643 | \r |
| 644 | case 5: // vsram\r |
| 645 | r = PicoMem.vsram;\r |
| 646 | for (; len; len--)\r |
| 647 | {\r |
| 648 | r[(a / 2) & 0x3f] = base[source++ & mask] & 0x7ff;\r |
| 649 | // AutoIncrement\r |
| 650 | a = (a+inc) & ~0x20000;\r |
| 651 | }\r |
| 652 | break;\r |
| 653 | \r |
| 654 | case 0x81: // vram 128k\r |
| 655 | for(; len; len--)\r |
| 656 | {\r |
| 657 | u16 d = base[source++ & mask];\r |
| 658 | VideoWriteVRAM128(a, d);\r |
| 659 | // AutoIncrement\r |
| 660 | a = (a+inc) & ~0x20000;\r |
| 661 | }\r |
| 662 | break;\r |
| 663 | \r |
| 664 | default:\r |
| 665 | if (pvid->type != 0 || (EL_LOGMASK & EL_VDPDMA))\r |
| 666 | elprintf(EL_VDPDMA|EL_ANOMALY, "DMA with bad type %i", pvid->type);\r |
| 667 | break;\r |
| 668 | }\r |
| 669 | // remember addr\r |
| 670 | pvid->addr = a;\r |
| 671 | pvid->addr_u = a >> 16;\r |
| 672 | }\r |
| 673 | \r |
| 674 | static void DmaCopy(int len)\r |
| 675 | {\r |
| 676 | struct PicoVideo *pvid=&Pico.video;\r |
| 677 | u32 a = pvid->addr | (pvid->addr_u << 16);\r |
| 678 | u8 *vr = (u8 *)PicoMem.vram;\r |
| 679 | u8 inc = pvid->reg[0xf];\r |
| 680 | int source;\r |
| 681 | elprintf(EL_VDPDMA, "DmaCopy len %i [%u]", len, SekCyclesDone());\r |
| 682 | \r |
| 683 | // XXX implement VRAM 128k? Is this even working? xfer/count still in bytes?\r |
| 684 | SekCyclesBurnRun(PicoVideoFIFOWrite(2*len, FQ_BGDMA, // 2 slots each (rd+wr)\r |
| 685 | PVS_CPUWR, SR_DMA | PVS_DMABG));\r |
| 686 | \r |
| 687 | source =pvid->reg[0x15];\r |
| 688 | source|=pvid->reg[0x16]<<8;\r |
| 689 | \r |
| 690 | for (; len; len--)\r |
| 691 | {\r |
| 692 | vr[(u16)a] = vr[(u16)(source++)];\r |
| 693 | if (((a^SATaddr) & SATmask) == 0)\r |
| 694 | UpdateSAT(a, ((u16 *)vr)[(u16)a >> 1]);\r |
| 695 | // AutoIncrement\r |
| 696 | a = (a+inc) & ~0x20000;\r |
| 697 | }\r |
| 698 | // remember addr\r |
| 699 | pvid->addr = a;\r |
| 700 | pvid->addr_u = a >> 16;\r |
| 701 | }\r |
| 702 | \r |
| 703 | static NOINLINE void DmaFill(int data)\r |
| 704 | {\r |
| 705 | struct PicoVideo *pvid=&Pico.video;\r |
| 706 | u32 a = pvid->addr | (pvid->addr_u << 16), e;\r |
| 707 | u8 *vr = (u8 *)PicoMem.vram;\r |
| 708 | u8 high = (u8)(data >> 8);\r |
| 709 | u8 inc = pvid->reg[0xf];\r |
| 710 | int source;\r |
| 711 | int len, l;\r |
| 712 | \r |
| 713 | len = GetDmaLength();\r |
| 714 | elprintf(EL_VDPDMA, "DmaFill len %i inc %i [%u]", len, inc, SekCyclesDone());\r |
| 715 | \r |
| 716 | SekCyclesBurnRun(PicoVideoFIFOWrite(len, FQ_BGDMA, // 1 slot each (wr)\r |
| 717 | PVS_CPUWR | PVS_DMAFILL, SR_DMA | PVS_DMABG));\r |
| 718 | \r |
| 719 | switch (pvid->type)\r |
| 720 | {\r |
| 721 | case 1: // vram\r |
| 722 | e = a + len-1;\r |
| 723 | if (inc == 1 && !((a ^ e) >> 16) &&\r |
| 724 | ((a >= SATaddr + 0x280) | (e < SATaddr)))\r |
| 725 | {\r |
| 726 | // most used DMA mode\r |
| 727 | memset(vr + (u16)a, high, len);\r |
| 728 | a += len;\r |
| 729 | break;\r |
| 730 | }\r |
| 731 | for (l = len; l; l--) {\r |
| 732 | // Write upper byte to adjacent address\r |
| 733 | // (here we are byteswapped, so address is already 'adjacent')\r |
| 734 | vr[(u16)a] = high;\r |
| 735 | if (((a^SATaddr) & SATmask) == 0)\r |
| 736 | UpdateSAT(a, ((u16 *)vr)[(u16)a >> 1]);\r |
| 737 | \r |
| 738 | // Increment address register\r |
| 739 | a = (a+inc) & ~0x20000;\r |
| 740 | }\r |
| 741 | break;\r |
| 742 | case 3: // cram\r |
| 743 | Pico.m.dirtyPal = 1;\r |
| 744 | data &= 0xeee;\r |
| 745 | for (l = len; l; l--) {\r |
| 746 | PicoMem.cram[(a/2) & 0x3f] = data;\r |
| 747 | \r |
| 748 | // Increment address register\r |
| 749 | a = (a+inc) & ~0x20000;\r |
| 750 | }\r |
| 751 | break;\r |
| 752 | case 5: { // vsram\r |
| 753 | data &= 0x7ff;\r |
| 754 | for (l = len; l; l--) {\r |
| 755 | PicoMem.vsram[(a/2) & 0x3f] = data;\r |
| 756 | \r |
| 757 | // Increment address register\r |
| 758 | a = (a+inc) & ~0x20000;\r |
| 759 | }\r |
| 760 | break;\r |
| 761 | }\r |
| 762 | case 0x81: // vram 128k\r |
| 763 | for (l = len; l; l--) {\r |
| 764 | VideoWriteVRAM128(a, data);\r |
| 765 | \r |
| 766 | // Increment address register\r |
| 767 | a = (a+inc) & ~0x20000;\r |
| 768 | }\r |
| 769 | break;\r |
| 770 | default:\r |
| 771 | a += len * inc;\r |
| 772 | break;\r |
| 773 | }\r |
| 774 | \r |
| 775 | // remember addr\r |
| 776 | pvid->addr = a;\r |
| 777 | pvid->addr_u = a >> 16;\r |
| 778 | // register update\r |
| 779 | pvid->reg[0x13] = pvid->reg[0x14] = 0;\r |
| 780 | source = pvid->reg[0x15];\r |
| 781 | source |= pvid->reg[0x16] << 8;\r |
| 782 | source += len;\r |
| 783 | pvid->reg[0x15] = source;\r |
| 784 | pvid->reg[0x16] = source >> 8;\r |
| 785 | }\r |
| 786 | \r |
| 787 | // VDP command handling\r |
| 788 | \r |
| 789 | static NOINLINE void CommandDma(void)\r |
| 790 | {\r |
| 791 | struct PicoVideo *pvid = &Pico.video;\r |
| 792 | u32 len, method;\r |
| 793 | u32 source;\r |
| 794 | \r |
| 795 | PicoVideoFIFOSync(SekCyclesDone()-Pico.t.m68c_line_start);\r |
| 796 | if (pvid->status & SR_DMA) {\r |
| 797 | elprintf(EL_VDPDMA, "Dma overlap, left=%d @ %06x",\r |
| 798 | VdpFIFO.fifo_total, SekPc);\r |
| 799 | VdpFIFO.fifo_total = VdpFIFO.fifo_ql = 0;\r |
| 800 | pvid->status &= ~PVS_DMAFILL;\r |
| 801 | }\r |
| 802 | \r |
| 803 | len = GetDmaLength();\r |
| 804 | source = pvid->reg[0x15];\r |
| 805 | source |= pvid->reg[0x16] << 8;\r |
| 806 | source |= pvid->reg[0x17] << 16;\r |
| 807 | \r |
| 808 | method=pvid->reg[0x17]>>6;\r |
| 809 | if (method < 2)\r |
| 810 | DmaSlow(len, source << 1); // 68000 to VDP\r |
| 811 | else if (method == 3)\r |
| 812 | DmaCopy(len); // VRAM Copy\r |
| 813 | else {\r |
| 814 | pvid->status |= SR_DMA|PVS_DMAFILL;\r |
| 815 | return;\r |
| 816 | }\r |
| 817 | source += len;\r |
| 818 | pvid->reg[0x13] = pvid->reg[0x14] = 0;\r |
| 819 | pvid->reg[0x15] = source;\r |
| 820 | pvid->reg[0x16] = source >> 8;\r |
| 821 | }\r |
| 822 | \r |
| 823 | static NOINLINE void CommandChange(struct PicoVideo *pvid)\r |
| 824 | {\r |
| 825 | unsigned int cmd, addr;\r |
| 826 | \r |
| 827 | cmd = pvid->command;\r |
| 828 | \r |
| 829 | // Get type of transfer 0xc0000030 (v/c/vsram read/write)\r |
| 830 | pvid->type = (u8)(((cmd >> 2) & 0xc) | (cmd >> 30));\r |
| 831 | if (pvid->type == 1) // vram\r |
| 832 | pvid->type |= pvid->reg[1] & 0x80; // 128k\r |
| 833 | \r |
| 834 | // Get address 0x3fff0003\r |
| 835 | addr = (cmd >> 16) & 0x3fff;\r |
| 836 | addr |= (cmd << 14) & 0xc000;\r |
| 837 | pvid->addr = (u16)addr;\r |
| 838 | pvid->addr_u = (u8)((cmd >> 2) & 1);\r |
| 839 | }\r |
| 840 | \r |
| 841 | // VDP interface\r |
| 842 | \r |
| 843 | static inline int InHblank(int offs)\r |
| 844 | {\r |
| 845 | // check if in left border (14 pixels) or HBLANK (86 pixels), 116 68k cycles\r |
| 846 | return SekCyclesDone() - Pico.t.m68c_line_start <= offs;\r |
| 847 | }\r |
| 848 | \r |
| 849 | void PicoVideoSync(int skip)\r |
| 850 | {\r |
| 851 | struct VdpFIFO *vf = &VdpFIFO;\r |
| 852 | int lines = Pico.video.reg[1]&0x08 ? 240 : 224;\r |
| 853 | int last = Pico.m.scanline - (skip > 0);\r |
| 854 | \r |
| 855 | if (!(PicoIn.opt & POPT_ALT_RENDERER) && !PicoIn.skipFrame) {\r |
| 856 | if (last >= lines)\r |
| 857 | last = lines-1;\r |
| 858 | else // in active display, need to sync next frame as well\r |
| 859 | Pico.est.rendstatus |= PDRAW_SYNC_NEXT;\r |
| 860 | \r |
| 861 | //elprintf(EL_ANOMALY, "sync");\r |
| 862 | if (unlikely(linedisabled >= 0 && linedisabled <= last)) {\r |
| 863 | if (Pico.est.DrawScanline <= linedisabled) {\r |
| 864 | int sl = vf->fifo_hcounts[lineoffset/clkdiv];\r |
| 865 | PicoDrawSync(linedisabled, sl ? sl : 1, 0);\r |
| 866 | }\r |
| 867 | linedisabled = -1;\r |
| 868 | }\r |
| 869 | if (unlikely(lineenabled >= 0 && lineenabled <= last)) {\r |
| 870 | if (Pico.est.DrawScanline <= lineenabled) {\r |
| 871 | int sl = vf->fifo_hcounts[lineoffset/clkdiv];\r |
| 872 | PicoDrawSync(lineenabled, 0, sl ? sl : 1);\r |
| 873 | }\r |
| 874 | lineenabled = -1;\r |
| 875 | }\r |
| 876 | if (Pico.est.DrawScanline <= last)\r |
| 877 | PicoDrawSync(last, 0, 0);\r |
| 878 | }\r |
| 879 | if (skip >= 0)\r |
| 880 | Pico.est.rendstatus |= PDRAW_SYNC_NEEDED;\r |
| 881 | }\r |
| 882 | \r |
| 883 | PICO_INTERNAL_ASM void PicoVideoWrite(u32 a,unsigned short d)\r |
| 884 | {\r |
| 885 | struct PicoVideo *pvid=&Pico.video;\r |
| 886 | \r |
| 887 | //elprintf(EL_STATUS, "PicoVideoWrite [%06x] %04x [%u] @ %06x",\r |
| 888 | // a, d, SekCyclesDone(), SekPc);\r |
| 889 | \r |
| 890 | a &= 0x1c;\r |
| 891 | switch (a)\r |
| 892 | {\r |
| 893 | case 0x00: // Data port 0 or 2\r |
| 894 | if (pvid->pending) {\r |
| 895 | CommandChange(pvid);\r |
| 896 | pvid->pending=0;\r |
| 897 | }\r |
| 898 | \r |
| 899 | // try avoiding the sync if the data doesn't change.\r |
| 900 | // Writes to the SAT in VRAM are special since they update the SAT cache.\r |
| 901 | if ((pvid->reg[1]&0x40) &&\r |
| 902 | !(pvid->type == 1 && !(pvid->addr&1) && ((pvid->addr^SATaddr)&SATmask) && PicoMem.vram[pvid->addr>>1] == d) &&\r |
| 903 | !(pvid->type == 3 && PicoMem.cram[(pvid->addr>>1) & 0x3f] == (d & 0xeee)) &&\r |
| 904 | !(pvid->type == 5 && PicoMem.vsram[(pvid->addr>>1) & 0x3f] == (d & 0x7ff)))\r |
| 905 | // the vertical scroll value for this line must be read from VSRAM early,\r |
| 906 | // since the A/B tile row to be read depends on it. E.g. Skitchin, OD2\r |
| 907 | // in contrast, CRAM writes would have an immediate effect on the current\r |
| 908 | // pixel, so sync can be closer to start of actual image data\r |
| 909 | PicoVideoSync(InHblank(pvid->type == 3 ? 103 : 30)); // cram in Toy Story\r |
| 910 | \r |
| 911 | if (!(PicoIn.opt&POPT_DIS_VDP_FIFO))\r |
| 912 | {\r |
| 913 | VdpFIFO.fifo_data[++VdpFIFO.fifo_dx&3] = d;\r |
| 914 | SekCyclesBurnRun(PicoVideoFIFOWrite(1, pvid->type == 1, 0, PVS_CPUWR));\r |
| 915 | \r |
| 916 | elprintf(EL_ASVDP, "VDP data write: [%04x] %04x [%u] {%i} @ %06x",\r |
| 917 | pvid->addr, d, SekCyclesDone(), pvid->type, SekPc);\r |
| 918 | }\r |
| 919 | VideoWrite(d);\r |
| 920 | \r |
| 921 | // start DMA fill on write. NB VSRAM and CRAM fills use wrong FIFO data.\r |
| 922 | if (pvid->status & PVS_DMAFILL)\r |
| 923 | DmaFill(VdpFIFO.fifo_data[(VdpFIFO.fifo_dx + !!(pvid->type&~0x81))&3]);\r |
| 924 | \r |
| 925 | break;\r |
| 926 | \r |
| 927 | case 0x04: // Control (command) port 4 or 6\r |
| 928 | if (pvid->status & SR_DMA)\r |
| 929 | SekCyclesBurnRun(PicoVideoFIFORead()); // kludge, flush out running DMA\r |
| 930 | if (pvid->pending)\r |
| 931 | {\r |
| 932 | // Low word of command:\r |
| 933 | if (!(pvid->reg[1]&0x10))\r |
| 934 | d = (d&~0x80)|(pvid->command&0x80);\r |
| 935 | pvid->command &= 0xffff0000;\r |
| 936 | pvid->command |= d;\r |
| 937 | pvid->pending = 0;\r |
| 938 | CommandChange(pvid);\r |
| 939 | // Check for dma:\r |
| 940 | if (d & 0x80) {\r |
| 941 | PicoVideoSync(InHblank(93));\r |
| 942 | CommandDma();\r |
| 943 | }\r |
| 944 | }\r |
| 945 | else\r |
| 946 | {\r |
| 947 | if ((d&0xc000)==0x8000)\r |
| 948 | {\r |
| 949 | // Register write:\r |
| 950 | int num=(d>>8)&0x1f;\r |
| 951 | int dold=pvid->reg[num];\r |
| 952 | pvid->type=0; // register writes clear command (else no Sega logo in Golden Axe II)\r |
| 953 | if (num > 0x0a && !(pvid->reg[1]&4)) {\r |
| 954 | elprintf(EL_ANOMALY, "%02x written to reg %02x in SMS mode @ %06x", d, num, SekPc);\r |
| 955 | return;\r |
| 956 | }\r |
| 957 | \r |
| 958 | d &= 0xff;\r |
| 959 | \r |
| 960 | if (num == 1 && ((pvid->reg[1]^d)&0x40)) {\r |
| 961 | // handle line blanking before line rendering. Only the last switch\r |
| 962 | // before the 1st sync for other reasons is honoured. Switching after\r |
| 963 | // active area is on next line\r |
| 964 | int skip = InHblank(470); // Deadly Moves\r |
| 965 | PicoVideoSync(skip);\r |
| 966 | lineenabled = (d&0x40) ? Pico.m.scanline + !skip: -1;\r |
| 967 | linedisabled = (d&0x40) ? -1 : Pico.m.scanline + !skip;\r |
| 968 | lineoffset = (skip ? SekCyclesDone() - Pico.t.m68c_line_start : 0);\r |
| 969 | } else if (((1<<num) & 0x738ff) && pvid->reg[num] != d)\r |
| 970 | // VDP regs 0-7,11-13,16-18 influence rendering, ignore all others\r |
| 971 | PicoVideoSync(InHblank(93)); // Toy Story\r |
| 972 | pvid->reg[num] = d;\r |
| 973 | \r |
| 974 | switch (num)\r |
| 975 | {\r |
| 976 | case 0x00:\r |
| 977 | if ((~dold&d)&2) {\r |
| 978 | unsigned c = SekCyclesDone() - Pico.t.m68c_line_start;\r |
| 979 | pvid->hv_latch = VdpFIFO.fifo_hcounts[c/clkdiv] | (pvid->v_counter << 8);\r |
| 980 | }\r |
| 981 | elprintf(EL_INTSW, "hint_onoff: %i->%i [%u] pend=%i @ %06x", (dold&0x10)>>4,\r |
| 982 | (d&0x10)>>4, SekCyclesDone(), (pvid->pending_ints&0x10)>>4, SekPc);\r |
| 983 | goto update_irq;\r |
| 984 | case 0x01:\r |
| 985 | if ((d^dold)&0x40)\r |
| 986 | PicoVideoFIFOMode(d & 0x40, pvid->reg[12]&1);\r |
| 987 | if (!(pvid->status & PVS_VB2))\r |
| 988 | pvid->status &= ~SR_VB;\r |
| 989 | pvid->status |= ((d >> 3) ^ SR_VB) & SR_VB; // forced blanking\r |
| 990 | elprintf(EL_INTSW, "vint_onoff: %i->%i [%u] pend=%i @ %06x", (dold&0x20)>>5,\r |
| 991 | (d&0x20)>>5, SekCyclesDone(), (pvid->pending_ints&0x20)>>5, SekPc);\r |
| 992 | goto update_irq;\r |
| 993 | case 0x05:\r |
| 994 | case 0x06:\r |
| 995 | if (d^dold) Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r |
| 996 | break;\r |
| 997 | case 0x0c:\r |
| 998 | // renderers should update their palettes if sh/hi mode is changed\r |
| 999 | if ((d^dold)&8) Pico.m.dirtyPal = 1;\r |
| 1000 | if ((d^dold)&1) {\r |
| 1001 | PicoVideoFIFOMode(pvid->reg[1]&0x40, d & 1);\r |
| 1002 | Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r |
| 1003 | }\r |
| 1004 | break;\r |
| 1005 | default:\r |
| 1006 | return;\r |
| 1007 | }\r |
| 1008 | if (Pico.est.rendstatus & PDRAW_DIRTY_SPRITES) {\r |
| 1009 | SATaddr = ((pvid->reg[5]&0x7f) << 9) | ((pvid->reg[6]&0x20) << 11);\r |
| 1010 | SATmask = ~0x1ff;\r |
| 1011 | if (pvid->reg[12]&1)\r |
| 1012 | SATaddr &= ~0x200, SATmask &= ~0x200; // H40, zero lowest SAT bit\r |
| 1013 | //elprintf(EL_STATUS, "spritep moved to %04x", SATaddr);\r |
| 1014 | }\r |
| 1015 | return;\r |
| 1016 | \r |
| 1017 | update_irq:\r |
| 1018 | #ifndef EMU_CORE_DEBUG\r |
| 1019 | // update IRQ level; TODO hack, still fire irq if disabling now\r |
| 1020 | if (!SekShouldInterrupt() || SekIrqLevel < pvid->hint_irq)\r |
| 1021 | {\r |
| 1022 | int lines, pints, irq = 0;\r |
| 1023 | lines = (pvid->reg[1] & 0x20) | (pvid->reg[0] & 0x10);\r |
| 1024 | pints = pvid->pending_ints & lines;\r |
| 1025 | if (pints & 0x20) irq = 6;\r |
| 1026 | else if (pints & 0x10) irq = pvid->hint_irq;\r |
| 1027 | \r |
| 1028 | if (irq) {\r |
| 1029 | // VDP irqs have highest prio, just overwrite old level\r |
| 1030 | SekInterrupt(irq); // update line\r |
| 1031 | \r |
| 1032 | // TODO this is broken because cost of current insn isn't known here\r |
| 1033 | SekEndRun(21); // make it delayed\r |
| 1034 | } else if (SekIrqLevel >= pvid->hint_irq) {\r |
| 1035 | // no VDP irq, query lower irqs\r |
| 1036 | SekInterrupt(PicoIn.AHW & PAHW_PICO ? PicoPicoIrqAck(0) : 0);\r |
| 1037 | }\r |
| 1038 | }\r |
| 1039 | #endif\r |
| 1040 | }\r |
| 1041 | else\r |
| 1042 | {\r |
| 1043 | // High word of command:\r |
| 1044 | pvid->command&=0x0000ffff;\r |
| 1045 | pvid->command|=d<<16;\r |
| 1046 | pvid->pending=1;\r |
| 1047 | }\r |
| 1048 | }\r |
| 1049 | break;\r |
| 1050 | \r |
| 1051 | // case 0x08: // 08 0a - HV counter - lock up\r |
| 1052 | // case 0x0c: // 0c 0e - HV counter - lock up\r |
| 1053 | // case 0x10: // 10 12 - PSG - handled by caller\r |
| 1054 | // case 0x14: // 14 16 - PSG - handled by caller\r |
| 1055 | // case 0x18: // 18 1a - no effect?\r |
| 1056 | case 0x1c: // 1c 1e - debug\r |
| 1057 | pvid->debug = d;\r |
| 1058 | pvid->debug_p = 0;\r |
| 1059 | if (d & (1 << 6)) {\r |
| 1060 | pvid->debug_p |= PVD_KILL_A | PVD_KILL_B;\r |
| 1061 | pvid->debug_p |= PVD_KILL_S_LO | PVD_KILL_S_HI;\r |
| 1062 | }\r |
| 1063 | switch ((d >> 7) & 3) {\r |
| 1064 | case 1:\r |
| 1065 | pvid->debug_p &= ~(PVD_KILL_S_LO | PVD_KILL_S_HI);\r |
| 1066 | pvid->debug_p |= PVD_FORCE_S;\r |
| 1067 | break;\r |
| 1068 | case 2:\r |
| 1069 | pvid->debug_p &= ~PVD_KILL_A;\r |
| 1070 | pvid->debug_p |= PVD_FORCE_A;\r |
| 1071 | break;\r |
| 1072 | case 3:\r |
| 1073 | pvid->debug_p &= ~PVD_KILL_B;\r |
| 1074 | pvid->debug_p |= PVD_FORCE_B;\r |
| 1075 | break;\r |
| 1076 | }\r |
| 1077 | break;\r |
| 1078 | }\r |
| 1079 | }\r |
| 1080 | \r |
| 1081 | static u32 VideoSr(const struct PicoVideo *pv)\r |
| 1082 | {\r |
| 1083 | unsigned int hp = pv->reg[12]&1 ? hboff40*488.5/slots40 : hboff32*488.5/slots32;\r |
| 1084 | unsigned int hl = pv->reg[12]&1 ? hblen40*488.5/slots40 : hblen32*488.5/slots32;\r |
| 1085 | unsigned int c = SekCyclesDone() - Pico.t.m68c_line_start;\r |
| 1086 | u32 d;\r |
| 1087 | \r |
| 1088 | PicoVideoFIFOSync(c);\r |
| 1089 | d = (u16)pv->status;\r |
| 1090 | \r |
| 1091 | if (c - hp < hl)\r |
| 1092 | d |= SR_HB;\r |
| 1093 | \r |
| 1094 | if (VdpFIFO.fifo_total >= 4)\r |
| 1095 | d |= SR_FULL;\r |
| 1096 | else if (!VdpFIFO.fifo_total)\r |
| 1097 | d |= SR_EMPT;\r |
| 1098 | return d;\r |
| 1099 | }\r |
| 1100 | \r |
| 1101 | PICO_INTERNAL_ASM u32 PicoVideoRead(u32 a)\r |
| 1102 | {\r |
| 1103 | struct PicoVideo *pv = &Pico.video;\r |
| 1104 | a &= 0x1c;\r |
| 1105 | \r |
| 1106 | if (a == 0x04) // control port\r |
| 1107 | {\r |
| 1108 | u32 d = VideoSr(pv);\r |
| 1109 | if (pv->pending) {\r |
| 1110 | CommandChange(pv);\r |
| 1111 | pv->pending = 0;\r |
| 1112 | }\r |
| 1113 | elprintf(EL_SR, "SR read: %04x [%u] @ %06x", d, SekCyclesDone(), SekPc);\r |
| 1114 | return d;\r |
| 1115 | }\r |
| 1116 | \r |
| 1117 | if (a == 0x08)\r |
| 1118 | {\r |
| 1119 | unsigned int c;\r |
| 1120 | u32 d;\r |
| 1121 | \r |
| 1122 | c = SekCyclesDone() - Pico.t.m68c_line_start;\r |
| 1123 | if (pv->reg[0]&2)\r |
| 1124 | d = pv->hv_latch;\r |
| 1125 | else d = VdpFIFO.fifo_hcounts[c/clkdiv] | (pv->v_counter << 8);\r |
| 1126 | \r |
| 1127 | elprintf(EL_HVCNT, "hv: %02x %02x [%u] @ %06x", d, pv->v_counter, SekCyclesDone(), SekPc);\r |
| 1128 | return d;\r |
| 1129 | }\r |
| 1130 | \r |
| 1131 | if (a==0x00) // data port\r |
| 1132 | {\r |
| 1133 | return VideoRead(0);\r |
| 1134 | }\r |
| 1135 | \r |
| 1136 | return PicoRead16_floating(a | 0xc00000);\r |
| 1137 | }\r |
| 1138 | \r |
| 1139 | unsigned char PicoVideoRead8DataH(int is_from_z80)\r |
| 1140 | {\r |
| 1141 | return VideoRead(is_from_z80) >> 8;\r |
| 1142 | }\r |
| 1143 | \r |
| 1144 | unsigned char PicoVideoRead8DataL(int is_from_z80)\r |
| 1145 | {\r |
| 1146 | return VideoRead(is_from_z80);\r |
| 1147 | }\r |
| 1148 | \r |
| 1149 | unsigned char PicoVideoRead8CtlH(int is_from_z80)\r |
| 1150 | {\r |
| 1151 | struct PicoVideo *pv = &Pico.video;\r |
| 1152 | u8 d = VideoSr(pv) >> 8;\r |
| 1153 | if (pv->pending) {\r |
| 1154 | CommandChange(pv);\r |
| 1155 | pv->pending = 0;\r |
| 1156 | }\r |
| 1157 | elprintf(EL_SR, "SR read (h): %02x @ %06x", d, SekPc);\r |
| 1158 | return d;\r |
| 1159 | }\r |
| 1160 | \r |
| 1161 | unsigned char PicoVideoRead8CtlL(int is_from_z80)\r |
| 1162 | {\r |
| 1163 | struct PicoVideo *pv = &Pico.video;\r |
| 1164 | u8 d = VideoSr(pv);\r |
| 1165 | if (pv->pending) {\r |
| 1166 | CommandChange(pv);\r |
| 1167 | pv->pending = 0;\r |
| 1168 | }\r |
| 1169 | elprintf(EL_SR, "SR read (l): %02x @ %06x", d, SekPc);\r |
| 1170 | return d;\r |
| 1171 | }\r |
| 1172 | \r |
| 1173 | unsigned char PicoVideoRead8HV_H(int is_from_z80)\r |
| 1174 | {\r |
| 1175 | u32 d = Pico.video.v_counter;\r |
| 1176 | if (Pico.video.reg[0]&2)\r |
| 1177 | d = Pico.video.hv_latch >> 8;\r |
| 1178 | elprintf(EL_HVCNT, "vcounter: %02x [%u] @ %06x", d, SekCyclesDone(), SekPc);\r |
| 1179 | return d;\r |
| 1180 | }\r |
| 1181 | \r |
| 1182 | // FIXME: broken\r |
| 1183 | unsigned char PicoVideoRead8HV_L(int is_from_z80)\r |
| 1184 | {\r |
| 1185 | u32 d = SekCyclesDone() - Pico.t.m68c_line_start;\r |
| 1186 | if (Pico.video.reg[0]&2)\r |
| 1187 | d = Pico.video.hv_latch;\r |
| 1188 | else d = VdpFIFO.fifo_hcounts[d/clkdiv];\r |
| 1189 | elprintf(EL_HVCNT, "hcounter: %02x [%u] @ %06x", d, SekCyclesDone(), SekPc);\r |
| 1190 | return d;\r |
| 1191 | }\r |
| 1192 | \r |
| 1193 | void PicoVideoReset(void)\r |
| 1194 | {\r |
| 1195 | Pico.video.pending_ints=0;\r |
| 1196 | Pico.video.reg[1] &= ~0x40; // TODO verify display disabled after reset\r |
| 1197 | Pico.video.reg[10] = 0xff; // HINT is turned off after reset\r |
| 1198 | Pico.video.status = 0x3428 | Pico.m.pal; // 'always set' bits | vblank | collision | pal\r |
| 1199 | \r |
| 1200 | memset(&VdpFIFO, 0, sizeof(VdpFIFO));\r |
| 1201 | Pico.m.dirtyPal = 1;\r |
| 1202 | \r |
| 1203 | PicoDrawBgcDMA(NULL, 0, 0, 0, 0);\r |
| 1204 | PicoVideoFIFOMode(Pico.video.reg[1]&0x40, Pico.video.reg[12]&1);\r |
| 1205 | }\r |
| 1206 | \r |
| 1207 | void PicoVideoCacheSAT(int load)\r |
| 1208 | {\r |
| 1209 | struct PicoVideo *pv = &Pico.video;\r |
| 1210 | int l;\r |
| 1211 | \r |
| 1212 | SATaddr = ((pv->reg[5]&0x7f) << 9) | ((pv->reg[6]&0x20) << 11);\r |
| 1213 | SATmask = ~0x1ff;\r |
| 1214 | if (pv->reg[12]&1)\r |
| 1215 | SATaddr &= ~0x200, SATmask &= ~0x200; // H40, zero lowest SAT bit\r |
| 1216 | \r |
| 1217 | // rebuild SAT cache XXX wrong since cache and memory can differ\r |
| 1218 | for (l = 0; load && l < 2*80; l ++) {\r |
| 1219 | u16 addr = SATaddr + l*4;\r |
| 1220 | ((u16 *)VdpSATCache)[l*2 ] = PicoMem.vram[(addr>>1) ];\r |
| 1221 | ((u16 *)VdpSATCache)[l*2 + 1] = PicoMem.vram[(addr>>1) + 1];\r |
| 1222 | }\r |
| 1223 | \r |
| 1224 | Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r |
| 1225 | }\r |
| 1226 | \r |
| 1227 | void PicoVideoSave(void)\r |
| 1228 | {\r |
| 1229 | struct VdpFIFO *vf = &VdpFIFO;\r |
| 1230 | struct PicoVideo *pv = &Pico.video;\r |
| 1231 | int l, x;\r |
| 1232 | \r |
| 1233 | // account for all outstanding xfers XXX kludge, entry attr's not saved\r |
| 1234 | pv->fifo_cnt = pv->fifo_bgcnt = 0;\r |
| 1235 | for (l = vf->fifo_ql, x = vf->fifo_qx + l-1; l > 0; l--, x--) {\r |
| 1236 | int cnt = (vf->fifo_queue[x&7] >> 3);\r |
| 1237 | if (vf->fifo_queue[x&7] & FQ_BGDMA)\r |
| 1238 | pv->fifo_bgcnt += cnt;\r |
| 1239 | else\r |
| 1240 | pv->fifo_cnt += cnt;\r |
| 1241 | }\r |
| 1242 | }\r |
| 1243 | \r |
| 1244 | void PicoVideoLoad(void)\r |
| 1245 | {\r |
| 1246 | struct VdpFIFO *vf = &VdpFIFO;\r |
| 1247 | struct PicoVideo *pv = &Pico.video;\r |
| 1248 | int b = pv->type == 1;\r |
| 1249 | \r |
| 1250 | // convert former dma_xfers (why was this in PicoMisc anyway?)\r |
| 1251 | if (Pico.m.dma_xfers) {\r |
| 1252 | pv->fifo_cnt = Pico.m.dma_xfers << b;\r |
| 1253 | Pico.m.dma_xfers = 0;\r |
| 1254 | }\r |
| 1255 | \r |
| 1256 | // fake entries in the FIFO if there are outstanding transfers\r |
| 1257 | vf->fifo_ql = vf->fifo_qx = vf->fifo_total = 0;\r |
| 1258 | if (pv->fifo_cnt) {\r |
| 1259 | int wc = pv->fifo_cnt;\r |
| 1260 | vf->fifo_total = (wc+b) >> b;\r |
| 1261 | vf->fifo_queue[vf->fifo_qx + vf->fifo_ql] = (wc << 3) | b | FQ_FGDMA;\r |
| 1262 | vf->fifo_ql ++;\r |
| 1263 | if (vf->fifo_total > 4 && !(pv->status & (PVS_CPUWR|PVS_CPURD)))\r |
| 1264 | pv->status |= PVS_CPUWR;\r |
| 1265 | }\r |
| 1266 | if (pv->fifo_bgcnt) {\r |
| 1267 | int wc = pv->fifo_bgcnt;\r |
| 1268 | if (!vf->fifo_ql)\r |
| 1269 | pv->status |= PVS_DMABG;\r |
| 1270 | vf->fifo_queue[vf->fifo_qx + vf->fifo_ql] = (wc << 3) | FQ_BGDMA;\r |
| 1271 | vf->fifo_ql ++;\r |
| 1272 | }\r |
| 1273 | PicoVideoCacheSAT(1);\r |
| 1274 | vf->fifo_maxslot = 0;\r |
| 1275 | }\r |
| 1276 | // vim:shiftwidth=2:ts=2:expandtab\r |