Skip to content

Commit 7781ba2

Browse files
committed
usb2neogeo: add runtime_profile to app
1 parent c12d2d2 commit 7781ba2

2 files changed

Lines changed: 135 additions & 82 deletions

File tree

src/apps/usb2neogeo/app.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "core/router/router.h"
1010
#include "core/services/players/manager.h"
1111
#include "core/services/profiles/profile.h"
12+
#include "core/services/profiles/runtime_profile.h"
1213
#include "core/input_interface.h"
1314
#include "core/output_interface.h"
1415
#include "native/device/gpio/gpio_device.h"
@@ -33,7 +34,6 @@ static gpio_device_config_t gpio_gpio_config[GPIO_MAX_PLAYERS] = {
3334
.pin_r2 = P1_NEOGEO_B6_PIN,
3435

3536
// Meta Buttons
36-
3737
.pin_s1 = P1_NEOGEO_S1_PIN,
3838
.pin_s2 = P1_NEOGEO_S2_PIN,
3939
.pin_a1 = GPIO_DISABLED,
@@ -59,6 +59,12 @@ static const profile_config_t app_profile_config = {
5959
.shared_profiles = NULL,
6060
};
6161

62+
static const runtime_profile_config_t app_runtime_profile_config = {
63+
.output_configs = {
64+
[OUTPUT_TARGET_GPIO] = &neogeo_runtime_output_config,
65+
},
66+
};
67+
6268
// ============================================================================
6369
// APP INPUT INTERFACES
6470
// ============================================================================
@@ -131,6 +137,9 @@ void app_init(void)
131137
const char* active_name = profile_get_name(OUTPUT_TARGET_GPIO,
132138
profile_get_active_index(OUTPUT_TARGET_GPIO));
133139

140+
// Initialize runtime assignment service
141+
runtime_profile_init(&app_runtime_profile_config);
142+
134143
printf("[app:usb2neogeo] Initialization complete\n");
135144
printf("[app:usb2neogeo] Routing: %s\n", "SIMPLE (USB → NEOGEO+ adapter 1:1)");
136145
printf("[app:usb2neogeo] Player slots: %d (SHIFT mode - players shift on disconnect)\n", MAX_PLAYER_SLOTS);

src/native/device/gpio/gpio_device.c

Lines changed: 125 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
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
// ============================================================================
@@ -25,6 +26,22 @@
2526
static gpio_device_port_t gpio_ports[GPIO_MAX_PLAYERS];
2627
static 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
5773
static 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
197217
void 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-
//
217231
void 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){
229243
void 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

Comments
 (0)