1616#include "core/services/players/manager.h"
1717#include "core/services/profiles/profile.h"
1818#include "core/services/profiles/profile_indicator.h"
19+ #include "core/services/profiles/runtime_profile.h"
1920#include "core/services/codes/codes.h"
2021
2122// ============================================================================
2526static gpio_device_port_t gpio_ports [GPIO_MAX_PLAYERS ];
2627static bool initialized = false;
2728
29+ // Last raw input state received from the tap callback.
30+ // Used by gpio_device_task() for combo detection, cheat codes, and
31+ // autofire periodic re-application (oscillation while button is held).
32+ static uint32_t tap_last_buttons = 0 ;
33+ static uint8_t tap_last_lx = 128 ;
34+ static uint8_t tap_last_ly = 128 ;
35+ static uint8_t tap_last_rx = 128 ;
36+ static uint8_t tap_last_ry = 128 ;
37+ static uint8_t tap_last_l2 = 0 ;
38+ static uint8_t tap_last_r2 = 0 ;
39+ static uint8_t tap_last_rz = 0 ;
40+ static bool tap_has_update = false;
41+
42+ // Profile output buffer shared between tap callback and task loop.
43+ static profile_output_t gpio_mapped [GPIO_MAX_PLAYERS ];
44+
2845// ============================================================================
2946// PROFILE SYSTEM (Delegates to core profile service)
3047// ============================================================================
@@ -53,7 +70,6 @@ static const char* gpio_get_profile_name(uint8_t index) {
5370// Internal GPIO Functions
5471// ============================================================================
5572
56- // Initialize GPIO pins
5773static void gpioport_gpio_init (bool active_high )
5874{
5975 uint32_t gpio_mask = 0 ;
@@ -70,13 +86,12 @@ static void gpioport_gpio_init(bool active_high)
7086 gpio_disable_pulls (i );
7187 }
7288 }
73-
89+
7490 if (active_high ) {
7591 gpio_set_dir_out_masked (gpio_mask );
7692 } else {
7793 gpio_set_dir_in_masked (gpio_mask );
7894 }
79-
8095}
8196
8297// ============================================================================
@@ -87,7 +102,6 @@ void gpioport_init(gpio_device_port_t* port, gpio_device_config_t* config, bool
87102 port -> active_high = active_high ;
88103 port -> gpio_mask = 0 ;
89104
90- // Pin Mask
91105 port -> mask_du = GPIO_MASK (config -> pin_du );
92106 port -> mask_dd = GPIO_MASK (config -> pin_dd );
93107 port -> mask_dr = GPIO_MASK (config -> pin_dr );
@@ -121,70 +135,47 @@ void gpioport_init(gpio_device_port_t* port, gpio_device_config_t* config, bool
121135// ============================================================================
122136// PUSH-BASED OUTPUT VIA ROUTER TAP
123137// ============================================================================
124- // GPIO updates happen immediately when input arrives via router tap callback,
125- // eliminating the one-loop-iteration polling delay. The tap fires from within
126- // router_submit_input() on the same iteration input is received.
127-
128- // Last raw button state from tap — used by task loop for combo detection
129- static uint32_t tap_last_buttons = 0 ;
130- static bool tap_has_update = false;
131138
132- // Tap callback — fires immediately from router_submit_input().
133- // Must be fast: just apply profile + update GPIO. No printf or blocking.
134- static void __not_in_flash_func (gpio_tap_callback )(output_target_t output ,
135- uint8_t player_index ,
136- const input_event_t * event )
139+ // Select the active profile (runtime override → normal fallback), apply it,
140+ // and write GPIO for one player. Suppressed during mapping mode.
141+ static void gpio_apply_output (uint8_t player_index ,
142+ uint32_t buttons ,
143+ uint8_t lx , uint8_t ly ,
144+ uint8_t rx , uint8_t ry ,
145+ uint8_t l2 , uint8_t r2 , uint8_t rz )
137146{
138- (void )output ;
139-
140- if (player_index >= GPIO_MAX_PLAYERS ) return ;
141-
142- // Store raw buttons for combo detection in task loop
143- tap_last_buttons = event -> buttons ;
144- tap_has_update = true;
145-
146- // Only update GPIO if we have connected players
147- if (playersCount == 0 ) return ;
148-
149- // Apply profile remapping
150- const profile_t * profile = profile_get_active (OUTPUT_TARGET_GPIO );
151- profile_output_t mapped ;
152- profile_apply (profile , event -> buttons ,
153- event -> analog [ANALOG_LX ], event -> analog [ANALOG_LY ],
154- event -> analog [ANALOG_RX ], event -> analog [ANALOG_RY ],
155- event -> analog [ANALOG_L2 ], event -> analog [ANALOG_R2 ],
156- event -> analog [ANALOG_RZ ],
157- & mapped );
158-
147+ if (runtime_profile_is_active ()) return ;
148+ const profile_t * profile = runtime_profile_get_active (OUTPUT_TARGET_GPIO );
149+ if (!profile ) profile = profile_get_active (OUTPUT_TARGET_GPIO );
150+ if (!profile ) return ;
151+ profile_apply (profile , buttons , lx , ly , rx , ry , l2 , r2 , rz ,
152+ & gpio_mapped [player_index ]);
153+
154+ const profile_output_t * mapped = & gpio_mapped [player_index ];
159155 const gpio_device_port_t * port = & gpio_ports [player_index ];
160156 uint32_t gpio_buttons = 0 ;
161-
162- // Mapping the buttons (active-low: 0 = pressed)
163- gpio_buttons |= (mapped .buttons & JP_BUTTON_S2 ) ? port -> mask_s2 : 0 ;
164- gpio_buttons |= (mapped .buttons & JP_BUTTON_S1 ) ? port -> mask_s1 : 0 ;
165- gpio_buttons |= (mapped .buttons & JP_BUTTON_DD ) ? port -> mask_dd : 0 ;
166- gpio_buttons |= (mapped .buttons & JP_BUTTON_DL ) ? port -> mask_dl : 0 ;
167- gpio_buttons |= (mapped .buttons & JP_BUTTON_DU ) ? port -> mask_du : 0 ;
168- gpio_buttons |= (mapped .buttons & JP_BUTTON_DR ) ? port -> mask_dr : 0 ;
169- gpio_buttons |= (mapped .buttons & JP_BUTTON_B1 ) ? port -> mask_b1 : 0 ;
170- gpio_buttons |= (mapped .buttons & JP_BUTTON_B2 ) ? port -> mask_b2 : 0 ;
171- gpio_buttons |= (mapped .buttons & JP_BUTTON_B3 ) ? port -> mask_b3 : 0 ;
172- gpio_buttons |= (mapped .buttons & JP_BUTTON_B4 ) ? port -> mask_b4 : 0 ;
173- gpio_buttons |= (mapped .buttons & JP_BUTTON_L1 ) ? port -> mask_l1 : 0 ;
174- gpio_buttons |= (mapped .buttons & JP_BUTTON_R1 ) ? port -> mask_r1 : 0 ;
175- gpio_buttons |= (mapped .buttons & JP_BUTTON_L2 ) ? port -> mask_l2 : 0 ;
176- gpio_buttons |= (mapped .buttons & JP_BUTTON_R2 ) ? port -> mask_r2 : 0 ;
177- gpio_buttons |= (mapped .buttons & JP_BUTTON_L3 ) ? port -> mask_l3 : 0 ;
178- gpio_buttons |= (mapped .buttons & JP_BUTTON_R3 ) ? port -> mask_r3 : 0 ;
179- gpio_buttons |= (mapped .buttons & JP_BUTTON_L4 ) ? port -> mask_l4 : 0 ;
180- gpio_buttons |= (mapped .buttons & JP_BUTTON_R4 ) ? port -> mask_r4 : 0 ;
181- // D-pad from left analog stick (threshold at 64/192 from center 128)
182- // HID convention: 0=up, 128=center, 255=down
183- gpio_buttons |= (mapped .left_x < 64 ) ? port -> mask_dl : 0 ; // Dpad Left
184- gpio_buttons |= (mapped .left_x > 192 ) ? port -> mask_dr : 0 ; // Dpad Right
185- gpio_buttons |= (mapped .left_y < 64 ) ? port -> mask_du : 0 ; // Dpad Up
186- gpio_buttons |= (mapped .left_y > 192 ) ? port -> mask_dd : 0 ; // Dpad Down
187-
157+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_S2 ) ? port -> mask_s2 : 0 ;
158+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_S1 ) ? port -> mask_s1 : 0 ;
159+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_DD ) ? port -> mask_dd : 0 ;
160+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_DL ) ? port -> mask_dl : 0 ;
161+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_DU ) ? port -> mask_du : 0 ;
162+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_DR ) ? port -> mask_dr : 0 ;
163+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_B1 ) ? port -> mask_b1 : 0 ;
164+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_B2 ) ? port -> mask_b2 : 0 ;
165+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_B3 ) ? port -> mask_b3 : 0 ;
166+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_B4 ) ? port -> mask_b4 : 0 ;
167+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_L1 ) ? port -> mask_l1 : 0 ;
168+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_R1 ) ? port -> mask_r1 : 0 ;
169+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_L2 ) ? port -> mask_l2 : 0 ;
170+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_R2 ) ? port -> mask_r2 : 0 ;
171+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_L3 ) ? port -> mask_l3 : 0 ;
172+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_R3 ) ? port -> mask_r3 : 0 ;
173+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_L4 ) ? port -> mask_l4 : 0 ;
174+ gpio_buttons |= (mapped -> buttons & JP_BUTTON_R4 ) ? port -> mask_r4 : 0 ;
175+ gpio_buttons |= (mapped -> left_x < 64 ) ? port -> mask_dl : 0 ;
176+ gpio_buttons |= (mapped -> left_x > 192 ) ? port -> mask_dr : 0 ;
177+ gpio_buttons |= (mapped -> left_y < 64 ) ? port -> mask_du : 0 ;
178+ gpio_buttons |= (mapped -> left_y > 192 ) ? port -> mask_dd : 0 ;
188179 if (port -> active_high ) {
189180 gpio_put_masked (port -> gpio_mask , gpio_buttons );
190181 } else {
@@ -193,27 +184,50 @@ static void __not_in_flash_func(gpio_tap_callback)(output_target_t output,
193184 }
194185}
195186
187+ // Tap callback — fires immediately from router_submit_input().
188+ // Must be fast: just store state + apply profile + update GPIO. No printf or blocking.
189+ static void __not_in_flash_func (gpio_tap_callback )(output_target_t output ,
190+ uint8_t player_index ,
191+ const input_event_t * event )
192+ {
193+ (void )output ;
194+
195+ if (playersCount == 0 || player_index >= GPIO_MAX_PLAYERS ) return ;
196+
197+ // Store raw input for combo detection in task loop and autofire re-apply
198+ tap_last_buttons = event -> buttons ;
199+ tap_last_lx = event -> analog [ANALOG_LX ];
200+ tap_last_ly = event -> analog [ANALOG_LY ];
201+ tap_last_rx = event -> analog [ANALOG_RX ];
202+ tap_last_ry = event -> analog [ANALOG_RY ];
203+ tap_last_l2 = event -> analog [ANALOG_L2 ];
204+ tap_last_r2 = event -> analog [ANALOG_R2 ];
205+ tap_last_rz = event -> analog [ANALOG_RZ ];
206+ tap_has_update = true;
207+
208+ gpio_apply_output (player_index ,
209+ event -> buttons ,
210+ event -> analog [ANALOG_LX ], event -> analog [ANALOG_LY ],
211+ event -> analog [ANALOG_RX ], event -> analog [ANALOG_RY ],
212+ event -> analog [ANALOG_L2 ], event -> analog [ANALOG_R2 ],
213+ event -> analog [ANALOG_RZ ]);
214+ }
215+
196216// init for GPIO communication
197217void gpio_device_init ()
198- {
218+ {
199219 profile_set_player_count_callback (gpio_get_player_count_for_profile );
220+ runtime_profile_set_player_count_callback (gpio_get_player_count_for_profile );
200221
201- // Register exclusive tap for push-based GPIO updates — fires immediately from
202- // router_submit_input() instead of waiting for next task loop iteration.
203- // Exclusive: router skips storing to router_outputs[] since we never poll.
204222 router_set_tap_exclusive (OUTPUT_TARGET_GPIO , gpio_tap_callback );
205223
206224 #if CFG_TUSB_DEBUG >= 1
207- // Initialize chosen UART
208225 uart_init (UART_ID , BAUD_RATE );
209-
210- // Set the GPIO function for the UART pins
211226 gpio_set_function (UART_TX_PIN , GPIO_FUNC_UART );
212227 gpio_set_function (UART_RX_PIN , GPIO_FUNC_UART );
213228 #endif
214229}
215230
216- //
217231void gpio_device_init_pins (gpio_device_config_t * config , bool active_high ){
218232 for (int i = 0 ; i < GPIO_MAX_PLAYERS ; i ++ ) {
219233 gpio_device_port_t * port = & gpio_ports [i ];
@@ -229,19 +243,51 @@ void gpio_device_init_pins(gpio_device_config_t* config, bool active_high){
229243void gpio_device_task ()
230244{
231245 static uint32_t last_buttons = 0 ;
246+ static uint8_t last_l2 = 0 ;
247+ static uint8_t last_r2 = 0 ;
248+ static uint8_t last_lx = 128 ;
249+ static uint8_t last_ly = 128 ;
250+ static uint8_t last_rx = 128 ;
251+ static uint8_t last_ry = 128 ;
252+ static uint8_t last_rz = 0 ;
232253 bool had_update = false;
233254
234- // Pick up raw button state from tap callback
255+ // Pick up raw input state from tap callback
235256 if (tap_has_update ) {
236- last_buttons = tap_last_buttons ;
257+ last_buttons = tap_last_buttons ;
258+ last_l2 = tap_last_l2 ;
259+ last_r2 = tap_last_r2 ;
260+ last_lx = tap_last_lx ;
261+ last_ly = tap_last_ly ;
262+ last_rx = tap_last_rx ;
263+ last_ry = tap_last_ry ;
264+ last_rz = tap_last_rz ;
237265 tap_has_update = false;
238- had_update = true;
266+ had_update = true;
239267 }
240268
241- // Always check profile switching combo with last known state
242- // This ensures combo detection works even when controller doesn't send updates while buttons held
243269 if (playersCount > 0 ) {
244- profile_check_switch_combo (last_buttons );
270+ // Profile-switch combo is suppressed while mapping so SELECT
271+ // is exclusively reserved for the mapping trigger/cancel.
272+ if (!runtime_profile_is_active ()) {
273+ uint8_t before = profile_get_active_index (OUTPUT_TARGET_GPIO );
274+ profile_check_switch_combo (last_buttons );
275+ if (profile_get_active_index (OUTPUT_TARGET_GPIO ) != before ) {
276+ runtime_profile_clear ();
277+ }
278+ }
279+ runtime_profile_check_combo (last_buttons , last_l2 , last_r2 );
280+
281+ // Periodic re-apply: profile_apply reads platform_time_ms() so autofire
282+ // oscillates even when the USB driver stops sending reports (button held).
283+ // gpio_apply_output handles both runtime and normal profiles uniformly.
284+ for (int i = 0 ; i < playersCount && i < GPIO_MAX_PLAYERS ; i ++ ) {
285+ gpio_apply_output (i ,
286+ last_buttons ,
287+ last_lx , last_ly ,
288+ last_rx , last_ry ,
289+ last_l2 , last_r2 , last_rz );
290+ }
245291 }
246292
247293 // Run cheat code detection when we had new input
@@ -250,8 +296,6 @@ void gpio_device_task()
250296 }
251297}
252298
253- //
254-
255299//-----------------------------------------------------------------------------
256300// Core1 Entry Point
257301//-----------------------------------------------------------------------------
@@ -276,7 +320,7 @@ const OutputInterface gpio_output_interface = {
276320 .target = OUTPUT_TARGET_GPIO ,
277321 .init = gpio_device_init ,
278322 .core1_task = NULL ,
279- .task = gpio_device_task , // GPIO needs periodic scan detection task
323+ .task = gpio_device_task ,
280324 .get_rumble = NULL ,
281325 .get_player_led = NULL ,
282326 .get_profile_count = gpio_get_profile_count ,
0 commit comments