c28a7c81 |
1 | /* |
2 | * TeensyTAS, TAS input player for MegaDrive |
3 | * Copyright (c) 2014 notaz |
4 | * |
5 | * Permission is hereby granted, free of charge, to any person obtaining |
6 | * a copy of this software and associated documentation files (the |
7 | * "Software"), to deal in the Software without restriction, including |
8 | * without limitation the rights to use, copy, modify, merge, publish, |
9 | * distribute, sublicense, and/or sell copies of the Software, and to |
10 | * permit persons to whom the Software is furnished to do so, subject to |
11 | * the following conditions: |
12 | * |
13 | * The above copyright notice and this permission notice shall be |
14 | * included in all copies or substantial portions of the Software. |
15 | * |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
23 | * SOFTWARE. |
24 | */ |
25 | |
0c1e003e |
26 | #include <stdint.h> |
27 | #include <stdio.h> |
28 | #include <string.h> |
29 | #include "teensy3/core_pins.h" |
30 | #include "teensy3/usb_seremu.h" |
31 | #include "teensy3/usb_rawhid.h" |
f20de073 |
32 | #include "pkts.h" |
33 | |
e8e66d02 |
34 | #define noinline __attribute__((noinline)) |
35 | |
f20de073 |
36 | // use power of 2 |
37 | #define STREAM_BUF_SIZE 512 |
38 | #define STREAM_BUF_MASK (512 - 1) |
0c1e003e |
39 | |
9c4f55f4 |
40 | /* ?0SA 00DU, ?1CB RLDU */ |
e8e66d02 |
41 | #define STREAM_EL_SZ 2 |
42 | |
f20de073 |
43 | static struct { |
e8e66d02 |
44 | uint8_t stream_to[STREAM_BUF_SIZE][STREAM_EL_SZ]; |
45 | uint8_t stream_from[STREAM_BUF_SIZE][STREAM_EL_SZ]; |
46 | union { |
47 | uint8_t fixed_state[4]; |
48 | uint32_t fixed_state32; |
49 | }; |
72ef318c |
50 | union { |
51 | uint8_t pending_state[4]; |
52 | uint32_t pending_state32; |
53 | }; |
e8e66d02 |
54 | uint32_t stream_enable_to:1; |
55 | uint32_t stream_enable_from:1; |
f20de073 |
56 | uint32_t stream_started:1; |
e8e66d02 |
57 | uint32_t stream_ended:1; |
4af6d4e5 |
58 | uint32_t use_readinc:1; |
72ef318c |
59 | uint32_t use_pending:1; |
4af6d4e5 |
60 | uint32_t frame_cnt; |
f20de073 |
61 | uint32_t edge_cnt; |
e8e66d02 |
62 | uint32_t t_i; |
63 | uint32_t t_o; |
64 | uint32_t f_i; |
65 | uint32_t f_o; |
f20de073 |
66 | } g; |
67 | |
0c1e003e |
68 | ssize_t _write(int fd, const void *buf, size_t nbyte) |
69 | { |
70 | char tbuf[64]; |
71 | int ret; |
72 | |
73 | if (fd != 1 && fd != 2) { |
74 | snprintf(tbuf, sizeof(tbuf), "write to fd %d\n", fd); |
75 | usb_seremu_write(tbuf, strlen(tbuf)); |
76 | } |
77 | |
78 | ret = usb_seremu_write(buf, nbyte); |
79 | return ret < 0 ? ret : nbyte; |
80 | } |
81 | |
82 | void yield(void) |
83 | { |
84 | } |
85 | |
72ef318c |
86 | /* portb handles TH */ |
4af6d4e5 |
87 | static void portb_isr_fixed(void) |
9c4f55f4 |
88 | { |
f20de073 |
89 | uint32_t isfr, th; |
9c4f55f4 |
90 | |
4af6d4e5 |
91 | isfr = PORTB_ISFR; |
92 | PORTB_ISFR = isfr; |
93 | th = (GPIOB_PDIR >> CORE_PIN0_BIT) & 1; |
94 | |
95 | GPIOD_PDOR = g.fixed_state[th]; |
96 | g.edge_cnt++; |
97 | } |
98 | |
8e400118 |
99 | static noinline void do_to_step(void) |
100 | { |
101 | g.frame_cnt++; |
102 | |
103 | g.t_o = (g.t_o + 1) & STREAM_BUF_MASK; |
104 | if (g.t_o == g.t_i) |
105 | // done |
106 | attachInterruptVector(IRQ_PORTB, portb_isr_fixed); |
107 | } |
108 | |
e8e66d02 |
109 | static void portb_isr_do_to_inc(void) |
4af6d4e5 |
110 | { |
111 | uint32_t isfr, th; |
9c4f55f4 |
112 | |
9c4f55f4 |
113 | isfr = PORTB_ISFR; |
114 | PORTB_ISFR = isfr; |
f20de073 |
115 | th = (GPIOB_PDIR >> CORE_PIN0_BIT) & 1; |
116 | |
e8e66d02 |
117 | GPIOD_PDOR = g.stream_to[g.t_o][th]; |
8e400118 |
118 | if (th) |
119 | do_to_step(); |
f20de073 |
120 | } |
121 | |
e8e66d02 |
122 | static void portb_isr_do_to(void) |
4af6d4e5 |
123 | { |
124 | uint32_t isfr, th; |
125 | |
126 | isfr = PORTB_ISFR; |
127 | PORTB_ISFR = isfr; |
128 | th = (GPIOB_PDIR >> CORE_PIN0_BIT) & 1; |
129 | |
e8e66d02 |
130 | GPIOD_PDOR = g.stream_to[g.t_o][th]; |
4af6d4e5 |
131 | g.edge_cnt++; |
132 | } |
133 | |
134 | static void portc_isr_nop(void) |
135 | { |
136 | uint32_t isfr; |
137 | |
138 | isfr = PORTC_ISFR; |
139 | PORTC_ISFR = isfr; |
140 | } |
141 | |
142 | // /vsync starts at line 235/259 (ntsc/pal), just as vcounter jumps back |
143 | // we care when it comes out (/vsync goes high) after 3 lines at 238/262 |
144 | static void portc_isr_frameinc(void) |
145 | { |
146 | uint32_t isfr; |
147 | |
148 | isfr = PORTC_ISFR; |
149 | PORTC_ISFR = isfr; |
150 | |
e8e66d02 |
151 | g.t_o = (g.t_o + 1) & STREAM_BUF_MASK; |
152 | if (g.t_o == g.t_i) { |
4af6d4e5 |
153 | attachInterruptVector(IRQ_PORTB, portb_isr_fixed); |
154 | attachInterruptVector(IRQ_PORTC, portc_isr_nop); |
155 | } |
156 | g.frame_cnt++; |
157 | } |
158 | |
e8e66d02 |
159 | /* "recording" data */ |
160 | static noinline void do_from_step(void) |
161 | { |
162 | uint32_t s; |
163 | |
164 | // should hopefully give atomic fixed_state read.. |
165 | s = g.fixed_state32; |
72ef318c |
166 | g.fixed_state32 = g.pending_state32; |
e8e66d02 |
167 | g.stream_from[g.f_i][0] = s; |
168 | g.stream_from[g.f_i][1] = s >> 8; |
169 | g.f_i = (g.f_i + 1) & STREAM_BUF_MASK; |
170 | } |
171 | |
172 | static void portb_isr_fixed_do_from(void) |
173 | { |
174 | uint32_t isfr, th; |
175 | |
176 | isfr = PORTB_ISFR; |
177 | PORTB_ISFR = isfr; |
178 | th = (GPIOB_PDIR >> CORE_PIN0_BIT) & 1; |
179 | |
180 | GPIOD_PDOR = g.fixed_state[th]; |
181 | if (th) |
182 | do_from_step(); |
183 | g.edge_cnt++; |
184 | } |
185 | |
186 | static void portc_isr_frameinc_do_from(void) |
187 | { |
188 | uint32_t isfr; |
189 | |
190 | isfr = PORTC_ISFR; |
191 | PORTC_ISFR = isfr; |
192 | |
193 | do_from_step(); |
194 | g.frame_cnt++; |
195 | } |
196 | |
f20de073 |
197 | static void udelay(uint32_t us) |
198 | { |
199 | uint32_t start = micros(); |
200 | |
201 | while ((micros() - start) < us) { |
202 | asm volatile("nop; nop; nop; nop"); |
203 | yield(); |
204 | } |
205 | } |
206 | |
207 | static void do_start_seq(void) |
208 | { |
209 | uint32_t edge_cnt_last; |
210 | uint32_t edge_cnt; |
211 | uint32_t start, t1, t2; |
212 | int tout; |
213 | |
214 | start = micros(); |
215 | edge_cnt = g.edge_cnt; |
216 | |
217 | /* magic value */ |
218 | g.fixed_state[0] = |
219 | g.fixed_state[1] = 0x25; |
220 | |
221 | for (tout = 10000; tout > 0; tout--) { |
222 | edge_cnt_last = edge_cnt; |
223 | udelay(100); |
224 | edge_cnt = g.edge_cnt; |
225 | |
226 | if (edge_cnt != edge_cnt_last) |
227 | continue; |
228 | if (!(GPIOB_PDIR & CORE_PIN0_BITMASK)) |
229 | break; |
230 | } |
231 | |
232 | g.fixed_state[0] = 0x33; |
233 | g.fixed_state[1] = 0x3f; |
234 | GPIOD_PDOR = 0x33; |
235 | |
236 | t1 = micros(); |
237 | if (tout == 0) { |
238 | printf("start_seq timeout1, t=%u\n", t1 - start); |
239 | return; |
240 | } |
241 | |
242 | for (tout = 100000; tout > 0; tout--) { |
243 | udelay(1); |
244 | |
245 | if (GPIOB_PDIR & CORE_PIN0_BITMASK) |
246 | break; |
247 | } |
248 | |
249 | t2 = micros(); |
250 | if (tout == 0) { |
251 | printf("start_seq timeout2, t1=%u, t2=%u\n", |
252 | t1 - start, t2 - t1); |
253 | return; |
254 | } |
255 | |
256 | //printf(" t1=%u, t2=%u\n", t1 - start, t2 - t1); |
257 | |
258 | if (g.stream_started) { |
259 | printf("got start_seq when already started\n"); |
260 | return; |
261 | } |
262 | |
e8e66d02 |
263 | if (!g.stream_enable_to && !g.stream_enable_from) { |
f20de073 |
264 | printf("got start_seq, without enable from USB\n"); |
265 | return; |
266 | } |
267 | |
e8e66d02 |
268 | if (g.stream_enable_to && g.t_i == g.t_o) { |
269 | printf("got start_seq while stream_to is empty\n"); |
270 | return; |
271 | } |
272 | |
273 | if (g.stream_enable_from && g.f_i != g.f_o) { |
274 | printf("got start_seq while stream_from is not empty\n"); |
f20de073 |
275 | return; |
276 | } |
277 | |
4af6d4e5 |
278 | __disable_irq(); |
f20de073 |
279 | g.stream_started = 1; |
e8e66d02 |
280 | if (g.stream_enable_to) { |
281 | if (g.use_readinc) { |
282 | attachInterruptVector(IRQ_PORTB, portb_isr_do_to_inc); |
283 | attachInterruptVector(IRQ_PORTC, portc_isr_nop); |
284 | } |
285 | else { |
286 | attachInterruptVector(IRQ_PORTB, portb_isr_do_to); |
287 | attachInterruptVector(IRQ_PORTC, portc_isr_frameinc); |
288 | } |
4af6d4e5 |
289 | } |
e8e66d02 |
290 | else if (g.stream_enable_from) { |
72ef318c |
291 | g.use_pending = 1; |
e8e66d02 |
292 | if (g.use_readinc) { |
293 | attachInterruptVector(IRQ_PORTB, |
294 | portb_isr_fixed_do_from); |
295 | attachInterruptVector(IRQ_PORTC, portc_isr_nop); |
296 | } |
297 | else { |
298 | attachInterruptVector(IRQ_PORTB, portb_isr_fixed); |
299 | attachInterruptVector(IRQ_PORTC, |
300 | portc_isr_frameinc_do_from); |
301 | } |
4af6d4e5 |
302 | } |
303 | __enable_irq(); |
f20de073 |
304 | } |
305 | |
e8e66d02 |
306 | // callers must disable IRQs |
307 | static void clear_state(void) |
308 | { |
309 | g.stream_enable_to = 0; |
310 | g.stream_enable_from = 0; |
e8e66d02 |
311 | g.stream_started = 0; |
312 | g.stream_ended = 0; |
72ef318c |
313 | g.use_readinc = 0; |
314 | g.use_pending = 0; |
e8e66d02 |
315 | g.t_i = g.t_o = 0; |
316 | g.f_i = g.f_o = 0; |
317 | g.frame_cnt = 0; |
318 | attachInterruptVector(IRQ_PORTB, portb_isr_fixed); |
319 | attachInterruptVector(IRQ_PORTC, portc_isr_nop); |
320 | } |
321 | |
322 | static int get_space_to(void) |
f20de073 |
323 | { |
e8e66d02 |
324 | return STREAM_BUF_SIZE - ((g.t_i - g.t_o) & STREAM_BUF_MASK); |
325 | } |
326 | |
327 | static int get_used_from(void) |
328 | { |
329 | return (g.f_i - g.f_o) & STREAM_BUF_MASK; |
f20de073 |
330 | } |
331 | |
332 | static void do_usb(void *buf) |
333 | { |
334 | struct tas_pkt *pkt = buf; |
e8e66d02 |
335 | uint32_t t_i, i; |
f20de073 |
336 | int space; |
337 | |
338 | switch (pkt->type) { |
339 | case PKT_FIXED_STATE: |
72ef318c |
340 | memcpy(&i, pkt->data, sizeof(i)); |
341 | if (g.use_pending) |
342 | g.pending_state32 = i; |
343 | else |
344 | g.fixed_state32 = i; |
f20de073 |
345 | break; |
346 | case PKT_STREAM_ENABLE: |
4af6d4e5 |
347 | __disable_irq(); |
e8e66d02 |
348 | clear_state(); |
4af6d4e5 |
349 | /* wait for start from MD */ |
e8e66d02 |
350 | g.stream_enable_to = pkt->enable.stream_to; |
351 | g.stream_enable_from = pkt->enable.stream_from; |
352 | g.use_readinc = pkt->enable.use_readinc; |
353 | __enable_irq(); |
354 | break; |
355 | case PKT_STREAM_ABORT: |
356 | __disable_irq(); |
357 | clear_state(); |
4af6d4e5 |
358 | __enable_irq(); |
f20de073 |
359 | break; |
360 | case PKT_STREAM_END: |
e8e66d02 |
361 | g.stream_ended = 1; |
4af6d4e5 |
362 | printf("end of stream\n"); |
f20de073 |
363 | break; |
e8e66d02 |
364 | case PKT_STREAM_DATA_TO: |
365 | t_i = g.t_i; |
366 | space = get_space_to(); |
367 | if (space <= pkt->size / STREAM_EL_SZ) { |
f20de073 |
368 | printf("got data pkt while space=%d\n", space); |
369 | return; |
370 | } |
e8e66d02 |
371 | for (i = 0; i < pkt->size / STREAM_EL_SZ; i++) { |
372 | memcpy(&g.stream_to[t_i++], |
f20de073 |
373 | pkt->data + i * STREAM_EL_SZ, |
374 | STREAM_EL_SZ); |
e8e66d02 |
375 | t_i &= STREAM_BUF_MASK; |
f20de073 |
376 | } |
e8e66d02 |
377 | g.t_i = t_i; |
f20de073 |
378 | break; |
379 | default: |
380 | printf("got unknown pkt type: %04x\n", pkt->type); |
381 | break; |
382 | } |
9c4f55f4 |
383 | } |
384 | |
0c1e003e |
385 | int main(void) |
386 | { |
beaf6d89 |
387 | uint32_t led_time = 0; |
388 | uint32_t scheck_time = 0; |
f20de073 |
389 | uint32_t edge_cnt_last; |
390 | uint32_t edge_cnt; |
391 | uint8_t buf[64]; |
1cb2822f |
392 | int ret; |
0c1e003e |
393 | |
394 | delay(1000); // wait for usb.. |
395 | |
f20de073 |
396 | /* ?0SA 00DU, ?1CB RLDU */ |
397 | g.fixed_state[0] = 0x33; |
398 | g.fixed_state[1] = 0x3f; |
399 | |
0c1e003e |
400 | printf("starting, rawhid: %d\n", usb_rawhid_available()); |
401 | |
4af6d4e5 |
402 | // md pin th tr tl r l d u vsync |
9c4f55f4 |
403 | // md bit* 6 5 4 3 2 1 0 |
4af6d4e5 |
404 | // t bit b16 d5 d4 d3 d2 d1 d0 c6 |
405 | // t pin 0 20 6 8 7 14 2 11 |
9c4f55f4 |
406 | // * - note: tl/tr mixed in most docs |
407 | pinMode(0, INPUT); |
4af6d4e5 |
408 | attachInterrupt(0, portb_isr_fixed, CHANGE); |
409 | attachInterruptVector(IRQ_PORTB, portb_isr_fixed); |
410 | pinMode(11, INPUT); |
411 | attachInterrupt(11, portc_isr_nop, RISING); |
412 | attachInterruptVector(IRQ_PORTC, portc_isr_nop); |
9c4f55f4 |
413 | |
4af6d4e5 |
414 | NVIC_SET_PRIORITY(IRQ_PORTB, 0); |
415 | NVIC_SET_PRIORITY(IRQ_PORTC, 16); |
8e400118 |
416 | SCB_SHPR1 = SCB_SHPR2 = SCB_SHPR3 = 0x10101010; |
f20de073 |
417 | |
9c4f55f4 |
418 | pinMode( 2, OUTPUT); |
419 | pinMode(14, OUTPUT); |
420 | pinMode( 7, OUTPUT); |
421 | pinMode( 8, OUTPUT); |
422 | pinMode( 6, OUTPUT); |
423 | pinMode(20, OUTPUT); |
424 | |
425 | // led |
426 | pinMode(13, OUTPUT); |
9c4f55f4 |
427 | |
428 | // CORE_PIN0_PORTSET CORE_PIN0_BITMASK PORTB_PCR16 |
4af6d4e5 |
429 | printf("GPIOB PDDR, PDIR: %08x %08x\n", GPIOB_PDIR, GPIOB_PDDR); |
9c4f55f4 |
430 | printf("GPIOC PDDR, PDIR: %08x %08x\n", GPIOC_PDIR, GPIOC_PDDR); |
431 | printf("GPIOD PDDR, PDIR: %08x %08x\n", GPIOD_PDIR, GPIOD_PDDR); |
432 | printf("PORTB_PCR16: %08x\n", PORTB_PCR16); |
4af6d4e5 |
433 | printf("PORTC_PCR6: %08x\n", PORTC_PCR6); |
8e400118 |
434 | printf("PORTD_PCR0: %08x\n", PORTD_PCR0); |
9c4f55f4 |
435 | |
f20de073 |
436 | asm("mrs %0, BASEPRI" : "=r"(ret)); |
8e400118 |
437 | printf("BASEPRI: %d, SHPR: %08x %08x %08x\n", |
438 | ret, SCB_SHPR1, SCB_SHPR2, SCB_SHPR3); |
0c1e003e |
439 | |
f20de073 |
440 | edge_cnt_last = g.edge_cnt; |
9c4f55f4 |
441 | |
1cb2822f |
442 | while (1) { |
f20de073 |
443 | struct tas_pkt pkt; |
beaf6d89 |
444 | uint32_t now; |
e8e66d02 |
445 | |
446 | while (g.stream_enable_to && !g.stream_ended |
447 | && get_space_to() > sizeof(pkt.data) / STREAM_EL_SZ) |
f20de073 |
448 | { |
beaf6d89 |
449 | if (g.t_i == g.t_o && g.frame_cnt != 0) { |
450 | printf("underflow detected\n"); |
451 | g.stream_enable_to = 0; |
452 | break; |
453 | } |
454 | |
f20de073 |
455 | pkt.type = PKT_STREAM_REQ; |
4af6d4e5 |
456 | pkt.req.frame = g.frame_cnt; |
457 | |
f20de073 |
458 | ret = usb_rawhid_send(&pkt, 1000); |
459 | if (ret != sizeof(pkt)) { |
460 | printf("send STREAM_REQ: %d\n", ret); |
461 | break; |
462 | } |
463 | |
464 | ret = usb_rawhid_recv(buf, 1000); |
465 | if (ret != 64) |
466 | printf("usb_rawhid_recv/s: %d\n", ret); |
467 | else |
468 | do_usb(buf); |
469 | } |
470 | |
e8e66d02 |
471 | while (g.stream_enable_from && !g.stream_ended |
472 | && get_used_from() >= sizeof(pkt.data) / STREAM_EL_SZ) |
473 | { |
474 | uint32_t f_o; |
475 | int i; |
476 | |
477 | f_o = g.f_o; |
478 | for (i = 0; i < sizeof(pkt.data); i += STREAM_EL_SZ) { |
479 | memcpy(pkt.data + i, &g.stream_from[f_o++], |
480 | STREAM_EL_SZ); |
481 | f_o &= STREAM_BUF_MASK; |
482 | } |
483 | g.f_o = f_o; |
484 | |
485 | pkt.type = PKT_STREAM_DATA_FROM; |
486 | pkt.size = i; |
487 | |
488 | ret = usb_rawhid_send(&pkt, 1000); |
489 | if (ret != sizeof(pkt)) { |
490 | printf("send DATA_FROM: %d\n", ret); |
491 | break; |
492 | } |
493 | } |
494 | |
beaf6d89 |
495 | now = millis(); |
496 | |
497 | // start condition check |
498 | if (now - scheck_time > 1000) { |
f20de073 |
499 | edge_cnt = g.edge_cnt; |
500 | //printf("e: %d th: %d\n", edge_cnt - edge_cnt_last, |
501 | // (GPIOB_PDIR >> CORE_PIN0_BIT) & 1); |
beaf6d89 |
502 | if ((g.stream_enable_to || g.stream_enable_from) |
503 | && !g.stream_started |
504 | && edge_cnt - edge_cnt_last > 10000) |
505 | { |
f20de073 |
506 | do_start_seq(); |
507 | edge_cnt = g.edge_cnt; |
508 | } |
509 | edge_cnt_last = edge_cnt; |
beaf6d89 |
510 | scheck_time = now; |
f20de073 |
511 | } |
512 | |
beaf6d89 |
513 | // led? |
514 | if (CORE_PIN13_PORTREG & CORE_PIN13_BITMASK) { |
515 | if ((int)(now - led_time) > 10) |
516 | CORE_PIN13_PORTCLEAR = CORE_PIN13_BITMASK; |
1cb2822f |
517 | } |
beaf6d89 |
518 | |
519 | // something on rawhid? |
520 | if (usb_rawhid_available() > 0) |
521 | { |
522 | ret = usb_rawhid_recv(buf, 20); |
523 | if (ret == 64) { |
524 | led_time = millis(); |
525 | CORE_PIN13_PORTSET = CORE_PIN13_BITMASK; |
526 | |
527 | do_usb(buf); |
528 | } |
529 | else { |
530 | printf("usb_rawhid_recv: %d\n", ret); |
531 | } |
1cb2822f |
532 | } |
0c1e003e |
533 | } |
1cb2822f |
534 | |
535 | return 0; |
0c1e003e |
536 | } |