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 }