git.y1.nz

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.