qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

myriad.c (10229B)


      1 // Copyright 2024 splitkb.com (support@splitkb.com)
      2 // SPDX-License-Identifier: GPL-2.0-or-later
      3 
      4 #include QMK_KEYBOARD_H
      5 
      6 #include "myriad.h"
      7 
      8 #include "i2c_master.h"
      9 #include "analog.h"
     10 
     11 typedef struct __attribute__((__packed__)) {
     12     char     magic_numbers[3];
     13     uint8_t  version_major;
     14     uint8_t  version_minor;
     15     uint8_t  version_patch;
     16     uint32_t checksum;
     17     uint16_t payload_length;
     18 } myriad_header_t;
     19 
     20 typedef struct __attribute__((__packed__)) {
     21     uint16_t vendor_id;
     22     uint16_t product_id;
     23     uint8_t  revision;
     24 } identity_record_t;
     25 
     26 static bool myriad_reader(uint8_t *data, uint16_t length) {
     27     const uint8_t  eeprom_address = 0x50; // 1010 000 - NOT shifted for R/W bit
     28     const uint16_t i2c_timeout    = 100;  // in milliseconds
     29 
     30     uint8_t num_pages      = (length / 256) + 1;
     31     uint8_t last_page_size = length % 256;
     32 
     33     for (int i = 0; i < num_pages; i++) {
     34         uint8_t  reg = 0; // We always start on a page boundary, so this is always zero
     35         uint16_t read_length;
     36         if (i == num_pages - 1) {
     37             read_length = last_page_size;
     38         } else {
     39             read_length = 256;
     40         }
     41         i2c_status_t s = i2c_read_register((eeprom_address + i) << 1, reg, &(data[i * 256]), read_length, i2c_timeout);
     42         if (s != I2C_STATUS_SUCCESS) {
     43             return false;
     44         }
     45     }
     46     return true;
     47 }
     48 
     49 static bool verify_header(myriad_header_t *header) {
     50     char     magic_numbers[] = {'M', 'Y', 'R'};
     51     uint8_t  version_major   = 1;
     52     uint16_t version_minor   = 0;
     53 
     54     for (int i = 0; i < sizeof(magic_numbers); i++) {
     55         // Check that the header starts with 'MYR', indicating that this is indeed a Myriad card.
     56         if (header->magic_numbers[i] != magic_numbers[i]) {
     57             return false;
     58         }
     59     }
     60 
     61     if (header->version_major != version_major || header->version_minor > version_minor) {
     62         // We obviously don't support cards with a different major version, because that indicates a breaking change.
     63         // We also don't support cards with HIGHER minor version,
     64         // as we are not guaranteed to be able to properly use all its features.
     65         return false;
     66     }
     67 
     68     if (header->payload_length > (2048 - sizeof(myriad_header_t))) {
     69         // The EEPROM chips are *at most* 16kb / 2kB large,
     70         // and some of that is taken up by the header.
     71         // We obviously can't have a payload which exceeds the EEPROM's size.
     72         return false;
     73     }
     74 
     75     return true;
     76 }
     77 
     78 // Sourced from https://en.wikipedia.org/wiki/Adler-32#Example_implementation
     79 static bool verify_checksum(uint8_t *data, uint16_t length, uint32_t checksum) {
     80     // Skip the header
     81     data += sizeof(myriad_header_t);
     82     length -= sizeof(myriad_header_t);
     83 
     84     const uint32_t MOD_ADLER = 65521;
     85 
     86     uint32_t a = 1, b = 0;
     87     size_t   index;
     88 
     89     // Process each byte of the data in order
     90     for (index = 0; index < length; ++index) {
     91         a = (a + data[index]) % MOD_ADLER;
     92         b = (b + a) % MOD_ADLER;
     93     }
     94     uint32_t calculated = ((b << 16) | a);
     95 
     96     return calculated == checksum;
     97 }
     98 
     99 // Locates a specific entry by type
    100 // Returns the offset of the PAYLOAD.
    101 static int16_t locate_entry(uint8_t entry_type, uint8_t entry_data_length, uint8_t *data, uint16_t minimum, uint16_t maximum) {
    102     if (minimum < sizeof(myriad_header_t)) {
    103         // Records must start *after* the header.
    104         // We silently allow this so the caller can just specify `0` as minimum for the first entry.
    105         minimum = sizeof(myriad_header_t);
    106     }
    107 
    108     uint16_t offset = minimum;
    109     while (offset < maximum) {
    110         if (data[offset] == entry_type) {
    111             // Type matches!
    112             if (data[offset + 1] == entry_data_length) {
    113                 // We found what we are looking for, so return payload reference.
    114                 return offset + 2;
    115             } else {
    116                 // The entry is the wrong length?
    117                 return -2;
    118             }
    119         } else {
    120             // No type match, so skip this one
    121             // We skip the type byte, the length byte, and any potential data (with length stored in the length byte)
    122             offset += 2 + data[offset + 1];
    123         }
    124     }
    125 
    126     // We hit the maximum and didn't find what we are looking for
    127     return -1;
    128 }
    129 
    130 static bool read_card_identity(uint8_t *data, uint16_t length, identity_record_t *record) {
    131     const uint8_t identity_type     = 0x01;
    132     const uint8_t entry_data_length = sizeof(identity_record_t);
    133     int16_t       result            = locate_entry(identity_type, entry_data_length, data, 0, length);
    134     if (result < 0) {
    135         return false;
    136     }
    137 
    138     for (int i = 0; i < sizeof(identity_record_t); i++) {
    139         ((uint8_t *)record)[i] = data[result + i];
    140     }
    141     return true;
    142 }
    143 
    144 static myriad_card_t _detect_myriad(void) {
    145     gpio_set_pin_input(MYRIAD_PRESENT);
    146     wait_ms(100);
    147 // The pin has an external pull-up, and a Myriad card shorts it to ground.
    148 #ifndef MYRIAD_OVERRIDE_PRESENCE
    149     if (gpio_read_pin(MYRIAD_PRESENT)) {
    150         return NONE;
    151     }
    152 #endif
    153 
    154     // Attempt to read header
    155     myriad_header_t header;
    156     if (!myriad_reader((uint8_t *)&header, sizeof(header))) {
    157         return INVALID;
    158     }
    159     if (!verify_header(&header)) {
    160         return INVALID;
    161     }
    162 
    163     // Now that we have determined that the header is valid
    164     // and we know the payload length, read the entire thing
    165     uint8_t  data[2048]; // Guaranteed to be large enough.
    166     uint16_t data_size = sizeof(header) + header.payload_length;
    167     if (!myriad_reader(data, data_size)) {
    168         return INVALID;
    169     }
    170     if (!verify_checksum(data, data_size, header.checksum)) {
    171         return INVALID;
    172     }
    173 
    174     identity_record_t identity;
    175     if (!read_card_identity(data, data_size, &identity)) {
    176         return INVALID;
    177     }
    178 
    179     if (identity.vendor_id == 0x0001 && identity.product_id == 0x0001) {
    180         return SKB_ENCODER;
    181     } else if (identity.vendor_id == 0x0001 && identity.product_id == 0x0002) {
    182         return SKB_JOYSTICK;
    183     } else if (identity.vendor_id == 0x0001 && identity.product_id == 0x0003) {
    184         return SKB_SWITCHES;
    185     }
    186 
    187     return UNKNOWN;
    188 }
    189 
    190 // Determine card presence & identity
    191 // Does NOT initialize the card for use!
    192 myriad_card_t detect_myriad(void) {
    193     static myriad_card_t card = UNINITIALIZED;
    194 
    195     if (card == UNINITIALIZED) {
    196         i2c_init();
    197         card = _detect_myriad();
    198     }
    199 
    200     return card;
    201 }
    202 
    203 static void myr_switches_init(void) {
    204     gpio_set_pin_input_high(MYRIAD_GPIO1); // S4
    205     gpio_set_pin_input_high(MYRIAD_GPIO2); // S2
    206     gpio_set_pin_input_high(MYRIAD_GPIO3); // S1
    207     gpio_set_pin_input_high(MYRIAD_GPIO4); // S3
    208 }
    209 
    210 static void myr_encoder_init(void) {
    211     gpio_set_pin_input_high(MYRIAD_GPIO1); // Press
    212     gpio_set_pin_input_high(MYRIAD_GPIO2); // A
    213     gpio_set_pin_input_high(MYRIAD_GPIO3); // B
    214 }
    215 
    216 static uint16_t myr_joystick_timer;
    217 static void     myr_joystick_init(void) {
    218     gpio_set_pin_input_high(MYRIAD_GPIO1); // Press
    219 
    220     myr_joystick_timer = timer_read();
    221 }
    222 
    223 // Make sure any card present is ready for use
    224 static myriad_card_t myriad_card_init(void) {
    225     static bool initialized = false;
    226 
    227     myriad_card_t card = detect_myriad();
    228     if (initialized) {
    229         return card;
    230     }
    231     initialized = true;
    232 
    233     switch (card) {
    234         case SKB_SWITCHES:
    235             myr_switches_init();
    236             break;
    237         case SKB_ENCODER:
    238             myr_encoder_init();
    239             break;
    240         case SKB_JOYSTICK:
    241             myr_joystick_init();
    242             break;
    243         default:
    244             break;
    245     }
    246     return card;
    247 }
    248 
    249 bool myriad_hook_matrix(matrix_row_t current_matrix[]) {
    250     myriad_card_t card = myriad_card_init();
    251     uint8_t       word = 0;
    252 
    253     if (card == SKB_SWITCHES) {
    254         word |= ((!gpio_read_pin(MYRIAD_GPIO3)) & 1) << 0;
    255         word |= ((!gpio_read_pin(MYRIAD_GPIO2)) & 1) << 1;
    256         word |= ((!gpio_read_pin(MYRIAD_GPIO4)) & 1) << 2;
    257         word |= ((!gpio_read_pin(MYRIAD_GPIO1)) & 1) << 3;
    258     } else if (card == SKB_ENCODER) {
    259         word |= ((!gpio_read_pin(MYRIAD_GPIO1)) & 1) << 4;
    260     } else if (card == SKB_JOYSTICK) {
    261         word |= ((!gpio_read_pin(MYRIAD_GPIO1)) & 1) << 4;
    262     } else {
    263         return false;
    264     }
    265 
    266     // 5 bytes of on-board keys, so we are the 6th
    267     bool matrix_has_changed = current_matrix[5] ^ word;
    268     current_matrix[5]       = word;
    269 
    270     return matrix_has_changed;
    271 }
    272 
    273 static pin_t encoders_pad_a[NUM_ENCODERS_MAX_PER_SIDE];
    274 static pin_t encoders_pad_b[NUM_ENCODERS_MAX_PER_SIDE];
    275 
    276 uint8_t myriad_hook_encoder(uint8_t index, bool pad_b) {
    277     if (myriad_card_init() != SKB_ENCODER) {
    278         return 0;
    279     }
    280     // 3 onboard encoders, so we are number 4
    281     pin_t pin         = pad_b ? encoders_pad_b[index] : encoders_pad_a[index];
    282     encoders_pad_a[3] = MYRIAD_GPIO2;
    283     encoders_pad_b[3] = MYRIAD_GPIO3;
    284     return gpio_read_pin(pin) ? 1 : 0;
    285 }
    286 
    287 report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) {
    288     if (myriad_card_init() != SKB_JOYSTICK) {
    289         return mouse_report;
    290     }
    291 
    292     if (timer_elapsed(myr_joystick_timer) < 10) {
    293         wait_ms(2);
    294         return mouse_report;
    295     }
    296 
    297     myr_joystick_timer = timer_read();
    298 
    299     // `analogReadPin` returns 0..1023
    300     int32_t y = (analogReadPin(MYRIAD_ADC1) - 512) * -1; // Note: axis is flipped
    301     int32_t x = analogReadPin(MYRIAD_ADC2) - 512;
    302     // Values are now -512..512
    303 
    304     // Create a dead zone in the middle where the mouse doesn't move
    305     const int16_t dead_zone = 10;
    306     if ((y < 0 && y > -1 * dead_zone) || (y > 0 && y < dead_zone)) {
    307         y = 0;
    308     }
    309     if ((x < 0 && x > -1 * dead_zone) || (x > 0 && x < dead_zone)) {
    310         x = 0;
    311     }
    312 
    313     // quadratic movement
    314     x = abs(x) * x / 5000;
    315     y = abs(y) * y / 5000;
    316 
    317     // Clamp final value to make sure we don't under/overflow
    318     if (y < -127) {
    319         y = -127;
    320     }
    321     if (y > 127) {
    322         y = 127;
    323     }
    324     if (x < -127) {
    325         x = -127;
    326     }
    327     if (x > 127) {
    328         x = 127;
    329     }
    330 
    331     mouse_report.x = x;
    332     mouse_report.y = y;
    333 
    334     return mouse_report;
    335 }
    336 
    337 bool pointing_device_driver_init(void) {
    338     gpio_set_pin_input(MYRIAD_ADC1); // Y
    339     gpio_set_pin_input(MYRIAD_ADC2); // X
    340     return true;
    341 }