gbdk-2020 | GameBoy Development Kit |
| download: https://git.y1.nz/archives/gbdk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
gbdk-lib/libc/targets/sm83/duck/megaduck_laptop_io.c
1 #include <gbdk/platform.h>
2 #include <gb/isr.h>
3
4 #include <stdio.h>
5 #include <stdint.h>
6 #include <stdbool.h>
7
8 #include <duck/laptop_io.h>
9
10 // volatile SFR __at(0xFF60) FF60_REG;
11
12 // TODO: namespace to megaduck
13 volatile bool duck_io_rx_byte_done;
14 volatile uint8_t duck_io_rx_byte;
15
16 uint8_t duck_io_rx_buf[DUCK_IO_LEN_RX_MAX];
17 uint8_t duck_io_rx_buf_len;
18
19 uint8_t duck_io_tx_buf[DUCK_IO_LEN_TX_MAX];
20 uint8_t duck_io_tx_buf_len;
21
22 uint8_t duck_io_priniter_init_result = DUCK_IO_PRINTER_FAIL; // Default printer to not connected
23
24
25 static void _delay_1_msec(void);
26 static bool duck_io_controller_init(void);
27
28 /** Serial link handler for receiving data send by the MegaDuck laptop peripheral
29
30 */
31 void duck_io_sio_isr(void) CRITICAL INTERRUPT {
32
33 // Save received data and update status flag
34 // Turn Serial ISR back off
35 duck_io_rx_byte = SB_REG;
36 duck_io_rx_byte_done = true;
37 set_interrupts(IE_REG & ~SIO_IFLAG);
38 }
39
40 ISR_VECTOR(VECTOR_SERIAL, duck_io_sio_isr)
41
42
43
44 static void _delay_1_msec(void) {
45 volatile uint8_t c = 75u;
46 while (c--);
47 }
48
49
50 // TODO: No longer in use(?)
51 //
52 // void duck_io_wait_done_with_timeout(uint8_t timeout_len_ms) {
53 // while (timeout_len_ms--) {
54 // _delay_1_msec();
55 // if (duck_io_rx_byte_done)
56 // return;
57 // }
58 // }
59
60
61 void duck_io_send_byte(uint8_t tx_byte) {
62
63 // FF60_REG = FF60_REG_BEFORE_XFER; // Seems optional in testing so far
64 SB_REG = tx_byte;
65 SC_REG = SIOF_XFER_START | SIOF_CLOCK_INT;
66
67 // TODO: the delay here seems inefficient, but need to find out actual timing on the wire first
68 _delay_1_msec();
69
70 // Restore to SIO input and clear pending interrupt
71 IF_REG = 0x00;
72 SC_REG = SIOF_XFER_START | SIOF_CLOCK_EXT;
73 }
74
75
76 void duck_io_enable_read_byte(void) {
77 // FF60_REG = FF60_REG_BEFORE_XFER;
78 SC_REG = (SIOF_XFER_START | SIOF_CLOCK_EXT);
79 IE_REG |= SIO_IFLAG;
80 IF_REG = 0x00;
81 enable_interrupts();
82 }
83
84
85 uint8_t duck_io_read_byte_no_timeout(void) {
86 CRITICAL {
87 duck_io_rx_byte_done = false;
88 }
89
90 duck_io_enable_read_byte();
91 while (!duck_io_rx_byte_done);
92 return duck_io_rx_byte;
93 }
94
95
96 bool duck_io_read_byte_with_msecs_timeout(uint8_t timeout_len_ms) {
97 uint8_t msec_counter;
98 CRITICAL {
99 duck_io_rx_byte_done = false;
100 }
101
102 duck_io_enable_read_byte();
103
104 while (timeout_len_ms--) {
105 // Each full run of the inner loop is ~ 1msec
106 msec_counter = 75u;
107 while (msec_counter--) {
108 if (duck_io_rx_byte_done)
109 return true;
110 }
111 }
112
113 return duck_io_rx_byte_done;
114 }
115
116
117 bool duck_io_send_byte_and_check_ack_msecs_timeout(uint8_t tx_byte, uint8_t timeout_len_ms, uint8_t expected_reply) {
118
119 duck_io_send_byte(tx_byte);
120
121 // A reply for the byte sent above should be incoming, fail if there is no reply
122 if (!duck_io_read_byte_with_msecs_timeout(timeout_len_ms)) return false;
123
124 // Then check reply byte vs expected reply
125 return (duck_io_rx_byte == expected_reply);
126 }
127
128
129 bool duck_io_send_cmd_and_buffer(uint8_t io_cmd) {
130
131 // Send buffer length + 2 (for length header and checksum bytes)
132 uint8_t packet_length = duck_io_tx_buf_len + 2;
133 uint8_t checksum_calc = packet_length; // Use total tx length (byte) as initial checksum
134
135 // Save interrupt enables and then set only Serial to ON
136 uint8_t int_enables_saved = IE_REG;
137 IE_REG = SIO_IFLAG;
138
139 // Send command to initiate buffer transfer, then check for reply
140 if (!duck_io_send_byte_and_check_ack_msecs_timeout(io_cmd, DUCK_IO_TIMEOUT_200_MSEC, DUCK_IO_REPLY_SEND_BUFFER_OK)) {
141 IE_REG = int_enables_saved;
142 return false;
143 }
144
145 // Send buffer length + 2 (for length header and checksum bytes)
146 _delay_1_msec; // Delay, perhaps for byte serial transfer time? (present in system rom)
147 if (!duck_io_send_byte_and_check_ack_msecs_timeout(packet_length, DUCK_IO_TIMEOUT_200_MSEC, DUCK_IO_REPLY_SEND_BUFFER_OK)) {
148 IE_REG = int_enables_saved;
149 return false;
150 }
151
152 // Send the buffer contents
153 uint8_t buffer_bytes_to_send = duck_io_tx_buf_len;
154 for (uint8_t idx = 0; idx < duck_io_tx_buf_len; idx++) {
155
156 // Update checksum with next byte
157 checksum_calc += duck_io_tx_buf[idx];
158
159 // Send a byte from the buffer
160 if (!duck_io_send_byte_and_check_ack_msecs_timeout(duck_io_tx_buf[idx], DUCK_IO_TIMEOUT_200_MSEC, DUCK_IO_REPLY_SEND_BUFFER_OK)) {
161 IE_REG = int_enables_saved;
162 return false;
163 }
164 }
165
166 // Done sending buffer bytes, last byte to send is checksum
167 // Tx Checksum Byte should == (((sum of all bytes except checksum) XOR 0xFF) + 1) [two's complement]
168 checksum_calc = ~checksum_calc + 1u; // 2's complement
169 // Note different expected reply value versus previous reply checks
170 if (!duck_io_send_byte_and_check_ack_msecs_timeout(checksum_calc, DUCK_IO_TIMEOUT_200_MSEC, DUCK_IO_REPLY_BUFFER_XFER_OK)) {
171 IE_REG = int_enables_saved;
172 return false;
173 }
174
175 // Success
176 IE_REG = int_enables_saved;
177 return true;
178 }
179
180
181 bool duck_io_send_cmd_and_receive_buffer(uint8_t io_cmd) {
182
183 uint8_t packet_length = 0u;
184 uint8_t checksum_calc = 0x00u;
185
186 // Reset global rx buffer length
187 duck_io_rx_buf_len = 0u;
188
189 // Save interrupt enables and then set only Serial to ON
190 uint8_t int_enables_saved = IE_REG;
191 IE_REG = SIO_IFLAG;
192
193 // _delay_1_msec() // Another mystery, ignore it for now
194 duck_io_send_byte(io_cmd);
195
196 // Fail if first rx byte timed out
197 if (duck_io_read_byte_with_msecs_timeout(DUCK_IO_TIMEOUT_100_MSEC)) {
198
199 // First rx byte will be length of all incoming bytes
200 if (duck_io_rx_byte <= DUCK_IO_LEN_RX_MAX) {
201
202 // Save rx byte as length and use to initialize checksum
203 // Reduce length by 1 (since it includes length byte already received)
204 checksum_calc = duck_io_rx_byte;
205 packet_length = duck_io_rx_byte - 1u;
206
207 while (packet_length--) {
208 // Wait for next rx byte
209 if (duck_io_read_byte_with_msecs_timeout(DUCK_IO_TIMEOUT_100_MSEC)) {
210 // Save rx byte to buffer and add to checksum
211 checksum_calc += duck_io_rx_byte;
212 duck_io_rx_buf[duck_io_rx_buf_len++] = duck_io_rx_byte;
213 } else {
214 // Error: Break out and set checksum so it fails test below (causing return with failure)
215 checksum_calc = 0xFFu;
216 break;
217 }
218 }
219
220 // Done receiving buffer bytes, last rx byte should be checksum
221 // Rx Checksum Byte should == (((sum of all bytes except checksum) XOR 0xFF) + 1) [two's complement]
222 // so ((sum of received bytes including checksum byte) should == -> unsigned 8 bit overflow -> 0x00
223 if (checksum_calc == 0x00u) {
224 // Return success
225 duck_io_send_byte(DUCK_IO_CMD_DONE_OR_OK);
226
227 // Reduce number of received bytes by 1 to strip off trailing checksum byte
228 duck_io_rx_buf_len--;
229 IE_REG = int_enables_saved;
230 return true;
231 }
232 }
233 }
234
235 // Something went wrong, error out
236 duck_io_send_byte(DUCK_IO_CMD_ABORT_OR_FAIL);
237 IE_REG = int_enables_saved;
238 return false;
239 }
240
241
242 static bool duck_io_controller_init(void) {
243 uint8_t counter;
244
245 IE_REG = SIO_IFLAG;
246 bool serial_system_init_is_ok = true;
247
248 // Send a count up sequence through the serial IO (0,1,2,3...255)
249 // Exit on 8 bit unsigned wraparound to 0x00
250 counter = 0u;
251 do {
252 duck_io_send_byte(counter++);
253 } while (counter != 0u);
254
255 // Then wait for a response
256 // Fail if reply back timed out or was not expected response
257 if (duck_io_read_byte_with_msecs_timeout(DUCK_IO_TIMEOUT_2_MSEC)) {
258 if (duck_io_rx_byte != DUCK_IO_REPLY_BOOT_OK) serial_system_init_is_ok = false;
259 } else
260 serial_system_init_is_ok = false;
261
262 // Send a command that seems to request a 255..0 countdown sequence from the external controller
263 if (serial_system_init_is_ok) {
264 duck_io_send_byte(DUCK_IO_CMD_INIT_START);
265
266 // Expects a reply sequence through the serial IO of (255,254,253...0)
267 counter = 255u;
268
269 // Exit on 8 bit unsigned wraparound to 0xFFu
270 do {
271 // Fail if reply back timed out or did not match expected counter
272 // TODO: OEM approach doesn't break out once a failure occurs,
273 // but maybe that's possible + sending the abort command early?
274 if (duck_io_read_byte_with_msecs_timeout(DUCK_IO_TIMEOUT_2_MSEC)) {
275 if (counter != duck_io_rx_byte) serial_system_init_is_ok = false;
276 } else
277 serial_system_init_is_ok = false;
278 counter--;
279 } while (counter != 255u);
280
281 // Check for failures during the reply sequence
282 // and send reply byte based on that
283 if (serial_system_init_is_ok)
284 duck_io_send_byte(DUCK_IO_CMD_DONE_OR_OK);
285 else
286 duck_io_send_byte(DUCK_IO_CMD_ABORT_OR_FAIL);
287 }
288
289 return serial_system_init_is_ok;
290 }
291
292
293 bool duck_io_laptop_init(void) {
294
295 uint8_t int_enables_saved;
296 bool duck_io_init_ok = true;
297
298 disable_interrupts();
299 int_enables_saved = IE_REG;
300 SC_REG = 0x00u;
301 SB_REG = 0x00u;
302
303 // Initialize Serially attached peripheral
304 duck_io_init_ok = duck_io_controller_init();
305 if (duck_io_init_ok) {
306
307 // Save response from some command
308 // (so far not seen being used in System ROM 32K Bank 0)
309 duck_io_send_byte(DUCK_IO_CMD_PRINT_INIT_EXT_IO);
310
311 // TODO: This wait with no timeout is how the System ROM does it,
312 // but it can probably be changed to a long delay and
313 // attempt to fail somewhat gracefully.
314 duck_io_read_byte_no_timeout();
315 duck_io_priniter_init_result = duck_io_rx_byte;
316 }
317
318 // Ignore the RTC init check for now
319
320 IE_REG = int_enables_saved;
321 enable_interrupts();
322
323 return (duck_io_init_ok);
324 }
325
326
327 uint8_t duck_io_printer_last_status(void) {
328 return (duck_io_priniter_init_result);
329 }
330
331
332 uint8_t duck_io_printer_query(void) {
333
334 // Query three times in a row, per System ROM behavior
335 // Might be doing some kind of initialization/test
336 for (uint8_t c = 0u; c < 3u; c++) {
337 // Delay per system rom behavior
338 delay(50);
339 duck_io_send_byte(DUCK_IO_CMD_PRINT_INIT_EXT_IO);
340 duck_io_read_byte_with_msecs_timeout(200);
341
342 // Cache result
343 duck_io_priniter_init_result = duck_io_rx_byte;
344 // Fail if the printer is not detected and/or not available
345 if (duck_io_rx_byte == 0) return duck_io_rx_byte;
346 }
347 return duck_io_rx_byte;
348 }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.