theseus75.c (8488B)
1 // Copyright 2023 Moritz Plattner (@ebastler), Alex Havermale (@haversnail) 2 // SPDX-License-Identifier: GPL-2.0-or-later 3 4 #include "quantum.h" 5 #include "transactions.h" 6 #include <stdio.h> 7 #include "print.h" 8 #include "split_util.h" 9 #include "usbpd.h" 10 11 typedef struct _kb_state_t { 12 usbpd_allowance_t allowance; 13 } kb_state_t; 14 15 kb_state_t kb_state; 16 17 const char* usbpd_str(usbpd_allowance_t allowance) { 18 switch (allowance) { 19 case USBPD_500MA: 20 return "500mA"; 21 case USBPD_1500MA: 22 return "1500mA"; 23 case USBPD_3000MA: 24 return "3000mA"; 25 default: 26 dprintf("Encountered unknown allowance enum value: %d\n", allowance); 27 return "UNKNOWN"; 28 } 29 } 30 31 void kb_state_slave_handler(uint8_t m2s_size, const void* m2s_buffer, uint8_t s2m_size, void* s2m_buffer) { 32 if (m2s_size == sizeof(kb_state_t)) { 33 memcpy(&kb_state, m2s_buffer, sizeof(kb_state_t)); 34 } else { 35 dprintf("Unexpected response in slave handler\n"); // TODO: add split debug logging 36 } 37 } 38 39 void keyboard_pre_init_kb(void) { 40 // Disable the PD peripheral in pre-init because its pins are being used in the matrix: 41 PWR->CR3 |= PWR_CR3_UCPD_DBDIS; 42 // Call the corresponding _user() function (see https://docs.qmk.fm/#/custom_quantum_functions) 43 keyboard_pre_init_user(); 44 } 45 46 void keyboard_post_init_kb(void) { 47 // Register keyboard state transaction: 48 transaction_register_rpc(RPC_ID_KB_STATE, kb_state_slave_handler); 49 50 // Set default state values: 51 kb_state.allowance = USBPD_500MA; 52 53 // If the keyboard is master, 54 if (is_keyboard_master()) { 55 // Turn on power to the split half and to underglow LEDs: 56 gpio_set_pin_output(PSW_PIN); 57 gpio_write_pin_high(PSW_PIN); 58 59 // Enable inputs used for current negotiation: 60 gpio_set_pin_input_high(USBPD_1_PIN); 61 gpio_set_pin_input_high(USBPD_2_PIN); 62 63 // Not needed in this mode (always high-Z with pull-up on PCB if port controller is sink) 64 gpio_set_pin_input_high(ID_PIN); 65 } else { 66 // Prepare output to enable power for USB output after negotiation: 67 gpio_set_pin_output(PSW_PIN); 68 69 // Switch the USB MUXes between hub and ports: 70 gpio_set_pin_output(USBSW_PIN); 71 gpio_write_pin_high(USBSW_PIN); 72 73 // Enable outputs used for current negotiation and default to 500mA: 74 gpio_set_pin_output(USBPD_1_PIN); 75 gpio_write_pin_high(USBPD_1_PIN); 76 gpio_set_pin_output(USBPD_2_PIN); 77 gpio_write_pin_high(USBPD_2_PIN); 78 79 // Use ID pin to check if client is detected (if low: USB source port powered): 80 gpio_set_pin_input_high(ID_PIN); 81 82 // Indicate that the hub is either self-powered or bus-powered based on whether the bus-power mode flag is enabled 83 // (configurable for users who would rather always have their device connect, regardless of whether they meet the specs): 84 gpio_set_pin_output(BUS_B_PIN); 85 if (DISABLE_BUS_POWER_MODE == TRUE) { 86 gpio_write_pin_high(BUS_B_PIN); 87 } else { 88 gpio_write_pin_low(BUS_B_PIN); 89 } 90 } 91 // Call the corresponding _user() function (see https://docs.qmk.fm/#/custom_quantum_functions) 92 keyboard_post_init_user(); 93 } 94 95 void housekeeping_task_kb(void) { 96 // Update any shared kb state to send to slave: 97 static uint32_t last_usbpd_allowance_check_time = 0; 98 if (timer_elapsed32(last_usbpd_allowance_check_time) > USBPD_ALLOWANCE_CHECK_INTERVAL) { 99 // On master side: check USBPD_1_PIN and USBPD_2_PIN to determine current negotiated with host 100 // (Can't use the usbpd_get_allowance() function, as this uses this uses the native CC PD interface 101 // of the G series MCU, while we're using dedicated port controllers instead): 102 if (is_keyboard_master()) { 103 usbpd_allowance_t allowance; 104 105 if (gpio_read_pin(USBPD_1_PIN)) { 106 allowance = USBPD_500MA; 107 } else if (gpio_read_pin(USBPD_2_PIN)) { 108 allowance = USBPD_1500MA; 109 } else { 110 allowance = USBPD_3000MA; 111 } 112 113 if (kb_state.allowance != allowance) { 114 printf("Host negotiated current: %s -> %s\n", usbpd_str(kb_state.allowance), usbpd_str(allowance)); 115 kb_state.allowance = allowance; 116 } 117 } else { 118 // On peripheral side - If ID_PIN is low: USB client negotiated 5V successfully -> enable power routing 119 // Check if PSW_PIN is not already high to avoid wasting time 120 if (!gpio_read_pin(ID_PIN) && !gpio_read_pin(PSW_PIN)) { 121 gpio_write_pin_high(PSW_PIN); 122 dprintf("USB downstream device connected\n"); // TODO: add split debug logging 123 } else if (gpio_read_pin(ID_PIN) && gpio_read_pin(PSW_PIN)) { 124 gpio_write_pin_low(PSW_PIN); 125 dprintf("USB downstream device disconnected\n"); // TODO: add split debug logging 126 } 127 }; 128 last_usbpd_allowance_check_time = timer_read32(); 129 }; 130 131 // Sync state from master to slave: 132 if (is_keyboard_master() && is_transport_connected()) { 133 bool needs_sync = false; 134 static uint32_t last_kb_state_sync_time; 135 static kb_state_t last_kb_state; 136 137 // Check if the state values are different: 138 if (memcmp(&kb_state, &last_kb_state, sizeof(kb_state_t))) { 139 needs_sync = true; 140 memcpy(&last_kb_state, &kb_state, sizeof(kb_state_t)); 141 } 142 143 // Sync state every so often regardless: 144 if (timer_elapsed32(last_kb_state_sync_time) > KB_STATE_SYNC_INTERVAL) { 145 needs_sync = true; 146 } 147 148 if (needs_sync) { 149 bool did_sync = transaction_rpc_send(RPC_ID_KB_STATE, sizeof(kb_state_t), &kb_state); 150 if (did_sync) { 151 dprintf("Synced to slave\n"); 152 last_kb_state_sync_time = timer_read32(); 153 } else { 154 dprintf("Failed to sync state\n"); 155 } 156 } 157 } 158 159 // Update the USBPD output pins on slave half whenever allowance has changed: 160 if (!is_keyboard_master()) { 161 static usbpd_allowance_t last_allowance; 162 163 if (last_allowance != kb_state.allowance) { 164 last_allowance = kb_state.allowance; 165 166 printf("Setting USB-PD output to %s (%s-powered)\n", usbpd_str(kb_state.allowance), kb_state.allowance == USBPD_500MA ? "bus" : "self"); // TODO: add split debug logging 167 168 switch (kb_state.allowance) { 169 default: 170 case USBPD_500MA: 171 // Set USBPD output to 500 mA: 172 gpio_write_pin_high(USBPD_1_PIN); 173 gpio_write_pin_high(USBPD_2_PIN); 174 if (DISABLE_BUS_POWER_MODE == TRUE) { 175 // Indicate hub is self-powered and devices can try to connect or fast charge: 176 gpio_write_pin_high(BUS_B_PIN); 177 } else { 178 // Indicate hub is bus-powered and devices should not try to connect or fast charge: 179 gpio_write_pin_low(BUS_B_PIN); 180 } 181 break; 182 case USBPD_1500MA: 183 // Set USBPD output to 500 mA: 184 gpio_write_pin_high(USBPD_1_PIN); 185 gpio_write_pin_high(USBPD_2_PIN); 186 // Indicate hub is self-powered and devices can try to connect or fast charge: 187 gpio_write_pin_high(BUS_B_PIN); 188 break; 189 case USBPD_3000MA: 190 // Set USBPD output to 1500 mA: 191 gpio_write_pin_low(USBPD_1_PIN); 192 gpio_write_pin_high(USBPD_2_PIN); 193 // Indicate hub is self-powered and devices can try to connect or fast charge: 194 gpio_write_pin_high(BUS_B_PIN); 195 break; 196 } 197 } 198 } 199 } 200 201 #ifdef RGB_MATRIX_ENABLE 202 bool rgb_matrix_indicators_kb(void) { 203 if (!rgb_matrix_indicators_user()) { 204 return false; 205 } 206 207 if (host_keyboard_led_state().caps_lock) { 208 rgb_matrix_set_color(CAPS_LOCK_LED_INDEX, INDICATOR_MAX_BRIGHTNESS, INDICATOR_MAX_BRIGHTNESS, INDICATOR_MAX_BRIGHTNESS); 209 } else { 210 rgb_matrix_set_color(CAPS_LOCK_LED_INDEX, 0, 0, 0); 211 } 212 return true; 213 } 214 #endif