diff --git a/Cargo.lock b/Cargo.lock
index 5c0f1c1..e69fe6e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,18 @@
 # It is not intended for manual editing.
 version = 3
 
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
 [[package]]
 name = "aho-corasick"
 version = "1.1.2"
@@ -77,6 +89,12 @@ version = "0.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
 
+[[package]]
+name = "bitfield"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac"
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -109,9 +127,9 @@ checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
 
 [[package]]
 name = "byteorder"
-version = "1.4.3"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "cfg-if"
@@ -136,7 +154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
 dependencies = [
  "bare-metal",
- "bitfield",
+ "bitfield 0.13.2",
  "embedded-hal 0.2.7",
  "volatile-register",
 ]
@@ -309,7 +327,8 @@ checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
 
 [[package]]
 name = "embassy-embedded-hal"
-version = "0.1.0"
+version = "0.2.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "defmt",
  "embassy-futures",
@@ -325,7 +344,8 @@ dependencies = [
 
 [[package]]
 name = "embassy-executor"
-version = "0.5.0"
+version = "0.6.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "cortex-m",
  "critical-section",
@@ -338,7 +358,8 @@ dependencies = [
 
 [[package]]
 name = "embassy-executor-macros"
-version = "0.4.0"
+version = "0.5.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "darling",
  "proc-macro2",
@@ -349,13 +370,15 @@ dependencies = [
 [[package]]
 name = "embassy-futures"
 version = "0.1.1"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "defmt",
 ]
 
 [[package]]
 name = "embassy-hal-internal"
-version = "0.1.0"
+version = "0.2.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "cortex-m",
  "critical-section",
@@ -366,10 +389,12 @@ dependencies = [
 [[package]]
 name = "embassy-net-driver"
 version = "0.2.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 
 [[package]]
 name = "embassy-net-driver-channel"
-version = "0.2.0"
+version = "0.3.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "embassy-futures",
  "embassy-net-driver",
@@ -378,7 +403,8 @@ dependencies = [
 
 [[package]]
 name = "embassy-rp"
-version = "0.1.0"
+version = "0.2.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "atomic-polyfill",
  "cfg-if",
@@ -403,7 +429,6 @@ dependencies = [
  "embedded-storage",
  "embedded-storage-async",
  "fixed",
- "futures",
  "nb 1.1.0",
  "pio",
  "pio-proc",
@@ -414,7 +439,8 @@ dependencies = [
 
 [[package]]
 name = "embassy-sync"
-version = "0.5.0"
+version = "0.6.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "cfg-if",
  "critical-section",
@@ -426,7 +452,8 @@ dependencies = [
 
 [[package]]
 name = "embassy-time"
-version = "0.3.0"
+version = "0.3.2"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "cfg-if",
  "critical-section",
@@ -444,6 +471,7 @@ dependencies = [
 [[package]]
 name = "embassy-time-driver"
 version = "0.1.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "document-features",
 ]
@@ -451,10 +479,12 @@ dependencies = [
 [[package]]
 name = "embassy-time-queue-driver"
 version = "0.1.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 
 [[package]]
 name = "embassy-usb"
-version = "0.1.0"
+version = "0.3.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "defmt",
  "embassy-futures",
@@ -469,6 +499,7 @@ dependencies = [
 [[package]]
 name = "embassy-usb-driver"
 version = "0.1.0"
+source = "git+https://git.naxdy.org/NaxdyOrg/embassy.git?branch=naxgcc-fw#16151cce0271573a66206aa6e85932c20bbf0c70"
 dependencies = [
  "defmt",
 ]
@@ -630,59 +661,12 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
 
-[[package]]
-name = "futures"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-sink",
- "futures-task",
- "futures-util",
-]
-
-[[package]]
-name = "futures-channel"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
-dependencies = [
- "futures-core",
- "futures-sink",
-]
-
 [[package]]
 name = "futures-core"
 version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
 
-[[package]]
-name = "futures-io"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
-
-[[package]]
-name = "futures-macro"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.52",
-]
-
-[[package]]
-name = "futures-sink"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
-
 [[package]]
 name = "futures-task"
 version = "0.3.30"
@@ -696,8 +680,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
 dependencies = [
  "futures-core",
- "futures-macro",
- "futures-sink",
  "futures-task",
  "pin-project-lite",
  "pin-utils",
@@ -733,6 +715,15 @@ dependencies = [
  "byteorder",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -768,7 +759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
 dependencies = [
  "equivalent",
- "hashbrown",
+ "hashbrown 0.14.3",
 ]
 
 [[package]]
@@ -1453,15 +1444,19 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
 
 [[package]]
 name = "usb-device"
-version = "0.2.9"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508"
+checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6"
+dependencies = [
+ "heapless",
+ "portable-atomic",
+]
 
 [[package]]
 name = "usbd-hid"
-version = "0.6.1"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "975bd411f4a939986751ea09992a24fa47c4d25c6ed108d04b4c2999a4fd0132"
+checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c"
 dependencies = [
  "serde",
  "ssmarshal",
@@ -1471,20 +1466,22 @@ dependencies = [
 
 [[package]]
 name = "usbd-hid-descriptors"
-version = "0.1.2"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcbee8c6735e90894fba04770bc41e11fd3c5256018856e15dc4dd1e6c8a3dd1"
+checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed"
 dependencies = [
- "bitfield",
+ "bitfield 0.14.0",
 ]
 
 [[package]]
 name = "usbd-hid-macros"
-version = "0.6.0"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "261079a9ada015fa1acac7cc73c98559f3a92585e15f508034beccf6a2ab75a2"
+checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38"
 dependencies = [
  "byteorder",
+ "hashbrown 0.13.2",
+ "log",
  "proc-macro2",
  "quote",
  "serde",
@@ -1687,3 +1684,23 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
 dependencies = [
  "tap",
 ]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.52",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 3233b94..c02148b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,33 +9,33 @@ edition = "2021"
 embassy-time = { version = "0.3.0", features = [
     "defmt",
     "defmt-timestamp-uptime",
-], path = "lib/embassy-rs/embassy-time" }
-embassy-embedded-hal = { version = "0.1.0", features = [
+], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
+embassy-embedded-hal = { version = "0.2.0", features = [
     "defmt",
-], path = "lib/embassy-rs/embassy-embedded-hal" }
-embassy-sync = { version = "0.5.0", features = [
+], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
+embassy-sync = { version = "0.6.0", features = [
     "defmt",
-], path = "lib/embassy-rs/embassy-sync" }
-embassy-executor = { version = "0.5.0", features = [
+], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
+embassy-executor = { version = "0.6.0", features = [
     "task-arena-size-32768",
     "arch-cortex-m",
     "executor-thread",
     "executor-interrupt",
     "defmt",
     "integrated-timers",
-], path = "lib/embassy-rs/embassy-executor" }
-embassy-rp = { version = "0.1.0", features = [
+], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
+embassy-rp = { version = "0.2.0", features = [
     "defmt",
     "unstable-pac",
     "time-driver",
     "critical-section-impl",
-], path = "lib/embassy-rs/embassy-rp" }
-embassy-usb = { version = "0.1.0", features = [
+], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
+embassy-usb = { version = "0.3.0", features = [
     "defmt",
-], path = "lib/embassy-rs/embassy-usb" }
+], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
 embassy-futures = { version = "0.1.0", features = [
     "defmt",
-], path = "lib/embassy-rs/embassy-futures" }
+], git = "https://git.naxdy.org/NaxdyOrg/embassy.git", branch = "naxgcc-fw" }
 defmt = "0.3"
 defmt-rtt = "0.4"
 fixed = "1.23.1"
@@ -48,7 +48,7 @@ libm = { version = "0.2.8" }
 cortex-m = { version = "0.7.6", features = ["inline-asm"] }
 cortex-m-rt = "0.7.0"
 panic-probe = { version = "0.3", features = ["print-defmt"] }
-packed_struct = { version = "0.10.1", default_features = false }
+packed_struct = { version = "0.10.1", default-features = false }
 format_no_std = "1.0.2"
 rand = { version = "0.8.5", default-features = false }
 
@@ -79,14 +79,14 @@ overflow-checks = false
 codegen-units = 8
 debug = false
 debug-assertions = false
-opt-level = 0
+opt-level = 3
 overflow-checks = false
 
 [profile.release.build-override]
 codegen-units = 8
 debug = false
 debug-assertions = false
-opt-level = 0
+opt-level = 3
 overflow-checks = false
 
 # cargo test
@@ -106,12 +106,3 @@ debug-assertions = false
 incremental = false
 lto = 'fat'
 opt-level = 3
-
-# [patch.crates-io]
-# embassy-rp = { path = "lib/embassy-rs/embassy-rp" }
-# embassy-time = { path = "lib/embassy-rs/embassy-time" }
-# embassy-embedded-hal = { path = "lib/embassy-rs/embassy-embedded-hal" }
-# embassy-usb = { path = "lib/embassy-rs/embassy-usb" }
-# embassy-sync = { path = "lib/embassy-rs/embassy-sync" }
-# embassy-executor = { path = "lib/embassy-rs/embassy-executor" }
-# embassy-futures = { path = "lib/embassy-rs/embassy-futures" }
diff --git a/flake.lock b/flake.lock
index 516b25e..9fe6565 100644
--- a/flake.lock
+++ b/flake.lock
@@ -35,24 +35,6 @@
         "type": "github"
       }
     },
-    "flake-utils_2": {
-      "inputs": {
-        "systems": "systems_2"
-      },
-      "locked": {
-        "lastModified": 1705309234,
-        "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
-        "type": "github"
-      },
-      "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "type": "github"
-      }
-    },
     "naersk": {
       "inputs": {
         "nixpkgs": [
@@ -91,11 +73,11 @@
     },
     "nixpkgs_2": {
       "locked": {
-        "lastModified": 1706487304,
-        "narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=",
+        "lastModified": 1718428119,
+        "narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "90f456026d284c22b3e3497be980b2e47d0b28ac",
+        "rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
         "type": "github"
       },
       "original": {
@@ -116,15 +98,14 @@
     },
     "rust-overlay": {
       "inputs": {
-        "flake-utils": "flake-utils_2",
         "nixpkgs": "nixpkgs_2"
       },
       "locked": {
-        "lastModified": 1710382258,
-        "narHash": "sha256-2FW1q+o34VBweYQiEkRaSEkNMq3ecrn83VzETeGiVbY=",
+        "lastModified": 1728700003,
+        "narHash": "sha256-Ox1pvEHxLK6lAdaKQW21Zvk65SPDag+cD8YA444R/og=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "8ce81e71ab04a7e906fae62da086d6ee5d6cfc21",
+        "rev": "fc1e58ebabe0cef4442eedea07556ff0c9eafcfe",
         "type": "github"
       },
       "original": {
@@ -147,21 +128,6 @@
         "repo": "default",
         "type": "github"
       }
-    },
-    "systems_2": {
-      "locked": {
-        "lastModified": 1681028828,
-        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
-        "owner": "nix-systems",
-        "repo": "default",
-        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-systems",
-        "repo": "default",
-        "type": "github"
-      }
     }
   },
   "root": "root",
diff --git a/src/config.rs b/src/config.rs
index 100ad5a..2f0962e 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -16,10 +16,6 @@ use packed_struct::{
 };
 
 use crate::{
-    gcc_hid::{
-        GcButtons1, GcButtons2, MUTEX_CONTROLLER_MODE, MUTEX_INPUT_CONSISTENCY_MODE,
-        SIGNAL_CHANGE_RUMBLE_STRENGTH,
-    },
     helpers::{PackedFloat, ToPackedFloatArray, ToRegularArray, XyValuePair},
     input::{
         read_ext_adc, Stick, StickAxis, FLOAT_ORIGIN, SPI_ACS_SHARED, SPI_CCS_SHARED, SPI_SHARED,
@@ -29,6 +25,10 @@ use crate::{
         LinearizedCalibration, NotchCalibration, NotchStatus, CALIBRATION_ORDER,
         NOTCH_ADJUSTMENT_ORDER, NO_OF_ADJ_NOTCHES, NO_OF_CALIBRATION_POINTS, NO_OF_NOTCHES,
     },
+    usb_comms::{
+        GcButtons1, GcButtons2, MUTEX_CONTROLLER_MODE, MUTEX_INPUT_CONSISTENCY_MODE,
+        SIGNAL_CHANGE_RUMBLE_STRENGTH,
+    },
     ADDR_OFFSET, FLASH_SIZE,
 };
 
@@ -39,7 +39,7 @@ use embassy_sync::{
 };
 use embassy_time::Timer;
 
-use crate::{gcc_hid::GcReport, input::CHANNEL_GCC_STATE};
+use crate::{input::CHANNEL_GCC_STATE, usb_comms::GcState};
 
 /// Whether we are currently calibrating the sticks. Updates are dispatched when the status changes.
 /// Initial status is assumed to be false.
@@ -81,7 +81,7 @@ const MAX_ANALOG_SCALER: u8 = 125;
 /// a certain mode.
 #[derive(Default, Debug, Clone, Format)]
 pub struct OverrideGcReportInstruction {
-    pub report: GcReport,
+    pub report: GcState,
     pub duration_ms: u64,
 }
 
@@ -503,7 +503,7 @@ enum NotchAdjustmentType {
 
 /// This needs to be incremented for ANY change to ControllerConfig
 /// else we risk loading uninitialized memory.
-pub const CONTROLLER_CONFIG_REVISION: u8 = 2;
+pub const CONTROLLER_CONFIG_REVISION: u8 = 1;
 
 #[derive(Debug, Clone, Format, PackedStruct)]
 #[packed_struct(endian = "msb")]
@@ -566,6 +566,7 @@ pub enum InputConsistencyMode {
     PC = 3,
 }
 
+/// Not saved, but set upon plugging in the controller.
 #[derive(Debug, Clone, Copy, Format, PrimitiveEnum_u8, PartialEq, Eq)]
 pub enum ControllerMode {
     /// Advertise itself as a GCC adapter with 1 controller (itself) connected.
@@ -591,8 +592,6 @@ pub struct ControllerConfig {
     pub astick_config: StickConfig,
     #[packed_field(size_bytes = "328")]
     pub cstick_config: StickConfig,
-    #[packed_field(size_bits = "8", ty = "enum")]
-    pub controller_mode: ControllerMode,
 }
 
 impl Default for ControllerConfig {
@@ -603,7 +602,6 @@ impl Default for ControllerConfig {
             astick_config: StickConfig::default(),
             rumble_strength: 9,
             cstick_config: StickConfig::default(),
-            controller_mode: ControllerMode::GcAdapter,
         }
     }
 }
@@ -651,6 +649,8 @@ impl ControllerConfig {
 /// Trait for providing button presses, used in the calibration process.
 trait ButtonPressProvider {
     /// Wait for a single button press.
+    // TODO: remove allow once this is used somewhere
+    #[allow(dead_code)]
     async fn wait_for_button_press(&mut self, button_to_wait_for: &AwaitableButtons);
 
     /// Wait for a single button release.
@@ -684,7 +684,7 @@ trait ButtonPressProvider {
 }
 
 impl<'a, T: RawMutex, const I: usize, const J: usize, const K: usize> ButtonPressProvider
-    for Subscriber<'a, T, GcReport, I, J, K>
+    for Subscriber<'a, T, GcState, I, J, K>
 {
     async fn wait_for_button_press(&mut self, button_to_wait_for: &AwaitableButtons) {
         loop {
@@ -784,7 +784,7 @@ impl<'a, T: RawMutex, const I: usize, const J: usize, const K: usize> ButtonPres
 }
 
 pub fn is_awaitable_button_pressed(
-    report: &GcReport,
+    report: &GcState,
     button_to_wait_for: &AwaitableButtons,
 ) -> bool {
     match button_to_wait_for {
@@ -1150,7 +1150,7 @@ async fn configuration_main_loop<
 >(
     current_config: &ControllerConfig,
     flash: &mut Flash<'static, FLASH, Async, FLASH_SIZE>,
-    gcc_subscriber: &mut Subscriber<'a, M, GcReport, C, S, P>,
+    gcc_subscriber: &mut Subscriber<'a, M, GcState, C, S, P>,
 ) -> ControllerConfig {
     let mut final_config = current_config.clone();
     let config_options = [
@@ -1208,7 +1208,7 @@ async fn configuration_main_loop<
             // exit
             0 => {
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1236,7 +1236,7 @@ async fn configuration_main_loop<
             // calibrate lstick
             1 => {
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1265,7 +1265,7 @@ async fn configuration_main_loop<
             // calibrate rstick
             2 => {
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1324,7 +1324,7 @@ async fn configuration_main_loop<
                 .clamp(-ABS_MAX_SNAPBACK, ABS_MAX_SNAPBACK);
 
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1410,7 +1410,7 @@ async fn configuration_main_loop<
                 .clamp(0, MAX_WAVESHAPING) as u8;
 
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1496,7 +1496,7 @@ async fn configuration_main_loop<
                 .clamp(0, MAX_SMOOTHING) as u8;
 
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1571,7 +1571,7 @@ async fn configuration_main_loop<
                 .clamp(-1, MAX_CARDINAL_SNAP);
 
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1627,7 +1627,7 @@ async fn configuration_main_loop<
                     as u8;
 
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1675,7 +1675,7 @@ async fn configuration_main_loop<
                 SIGNAL_CHANGE_RUMBLE_STRENGTH.signal(*to_adjust);
 
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1711,7 +1711,7 @@ async fn configuration_main_loop<
                 };
 
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1745,7 +1745,7 @@ async fn configuration_main_loop<
             // display waveshaping values on both sticks
             38 => {
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1771,7 +1771,7 @@ async fn configuration_main_loop<
             // display stick smoothing values on both sticks
             39 => {
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1797,7 +1797,7 @@ async fn configuration_main_loop<
             // display snapback values on both sticks
             40 => {
                 override_gcc_state_and_wait(&OverrideGcReportInstruction {
-                    report: GcReport {
+                    report: GcState {
                         trigger_r: 255,
                         trigger_l: 255,
                         buttons_2: GcButtons2 {
@@ -1854,11 +1854,6 @@ pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) {
         *m_input_consistency = Some(current_config.input_consistency_mode);
     }
 
-    {
-        let mut m_is_procon = MUTEX_CONTROLLER_MODE.lock().await;
-        *m_is_procon = Some(current_config.controller_mode);
-    }
-
     SIGNAL_CHANGE_RUMBLE_STRENGTH.signal(current_config.rumble_strength);
     SIGNAL_CONFIG_CHANGE.signal(current_config.clone());
 
@@ -1873,7 +1868,7 @@ pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) {
         info!("Entering config mode.");
 
         override_gcc_state_and_wait(&OverrideGcReportInstruction {
-            report: GcReport {
+            report: GcState {
                 trigger_r: 255,
                 trigger_l: 255,
                 buttons_2: GcButtons2 {
diff --git a/src/input.rs b/src/input.rs
index 874e17d..773607a 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -1,7 +1,7 @@
 use defmt::{debug, info, trace, Format};
 use embassy_futures::yield_now;
 use embassy_rp::{
-    gpio::{AnyPin, Input, Output, Pin},
+    gpio::{Input, Output},
     peripherals::SPI0,
     spi::{Blocking, Spi},
 };
@@ -16,19 +16,19 @@ use libm::{fmaxf, fminf};
 
 use crate::{
     config::{
-        ControllerConfig, InputConsistencyMode, OverrideGcReportInstruction, OverrideStickState,
-        SIGNAL_CONFIG_CHANGE, SIGNAL_IS_CALIBRATING, SIGNAL_OVERRIDE_GCC_STATE,
+        ControllerConfig, ControllerMode, InputConsistencyMode, OverrideGcReportInstruction,
+        OverrideStickState, SIGNAL_CONFIG_CHANGE, SIGNAL_IS_CALIBRATING, SIGNAL_OVERRIDE_GCC_STATE,
         SIGNAL_OVERRIDE_STICK_STATE,
     },
     filter::{run_waveshaping, FilterGains, KalmanState, WaveshapingValues, FILTER_GAINS},
-    gcc_hid::{GcReport, MUTEX_INPUT_CONSISTENCY_MODE},
     helpers::XyValuePair,
     input_filter::{DummyFilter, InputFilter},
     stick::{linearize, notch_remap, StickParams},
+    usb_comms::{GcState, MUTEX_CONTROLLER_MODE, MUTEX_INPUT_CONSISTENCY_MODE},
 };
 
 /// Used to send the button state to the usb task and the calibration task
-pub static CHANNEL_GCC_STATE: PubSubChannel<CriticalSectionRawMutex, GcReport, 1, 4, 1> =
+pub static CHANNEL_GCC_STATE: PubSubChannel<CriticalSectionRawMutex, GcState, 1, 4, 1> =
     PubSubChannel::new();
 
 /// Used to send the stick state from the stick task to the main input task
@@ -36,10 +36,8 @@ static SIGNAL_STICK_STATE: Signal<CriticalSectionRawMutex, StickState> = Signal:
 
 pub static SPI_SHARED: Mutex<ThreadModeRawMutex, Option<Spi<'static, SPI0, Blocking>>> =
     Mutex::new(None);
-pub static SPI_ACS_SHARED: Mutex<ThreadModeRawMutex, Option<Output<'static, AnyPin>>> =
-    Mutex::new(None);
-pub static SPI_CCS_SHARED: Mutex<ThreadModeRawMutex, Option<Output<'static, AnyPin>>> =
-    Mutex::new(None);
+pub static SPI_ACS_SHARED: Mutex<ThreadModeRawMutex, Option<Output<'static>>> = Mutex::new(None);
+pub static SPI_CCS_SHARED: Mutex<ThreadModeRawMutex, Option<Output<'static>>> = Mutex::new(None);
 
 const STICK_HYST_VAL: f32 = 0.3;
 pub const FLOAT_ORIGIN: f32 = 127.5;
@@ -84,18 +82,12 @@ pub enum StickAxis {
 
 #[inline(never)]
 #[link_section = ".time_critical.read_ext_adc"]
-pub fn read_ext_adc<
-    'a,
-    Acs: Pin,
-    Ccs: Pin,
-    I: embassy_rp::spi::Instance,
-    M: embassy_rp::spi::Mode,
->(
+pub fn read_ext_adc<'a, I: embassy_rp::spi::Instance, M: embassy_rp::spi::Mode>(
     which_stick: Stick,
     which_axis: StickAxis,
     spi: &mut Spi<'a, I, M>,
-    spi_acs: &mut Output<'a, Acs>,
-    spi_ccs: &mut Output<'a, Ccs>,
+    spi_acs: &mut Output<'a>,
+    spi_ccs: &mut Output<'a>,
 ) -> u16 {
     let mut buf = [0b11010000; 3];
 
@@ -352,19 +344,19 @@ async fn update_stick_states(
 
 #[allow(clippy::too_many_arguments)]
 fn update_button_states(
-    gcc_state: &mut GcReport,
-    btn_a: &Input<'_, AnyPin>,
-    btn_b: &Input<'_, AnyPin>,
-    btn_x: &Input<'_, AnyPin>,
-    btn_y: &Input<'_, AnyPin>,
-    btn_start: &Input<'_, AnyPin>,
-    btn_l: &Input<'_, AnyPin>,
-    btn_r: &Input<'_, AnyPin>,
-    btn_z: &Input<'_, AnyPin>,
-    btn_dleft: &Input<'_, AnyPin>,
-    btn_dright: &Input<'_, AnyPin>,
-    btn_dup: &Input<'_, AnyPin>,
-    btn_ddown: &Input<'_, AnyPin>,
+    gcc_state: &mut GcState,
+    btn_a: &Input<'_>,
+    btn_b: &Input<'_>,
+    btn_x: &Input<'_>,
+    btn_y: &Input<'_>,
+    btn_start: &Input<'_>,
+    btn_l: &Input<'_>,
+    btn_r: &Input<'_>,
+    btn_z: &Input<'_>,
+    btn_dleft: &Input<'_>,
+    btn_dright: &Input<'_>,
+    btn_dup: &Input<'_>,
+    btn_ddown: &Input<'_>,
 ) {
     gcc_state.buttons_1.button_a = btn_a.is_low();
     gcc_state.buttons_1.button_b = btn_b.is_low();
@@ -393,7 +385,7 @@ pub async fn input_integrity_benchmark() {
     loop {
         SIGNAL_OVERRIDE_GCC_STATE.signal(OverrideGcReportInstruction {
             report: {
-                let mut report = GcReport::default();
+                let mut report = GcState::default();
                 report.buttons_1.dpad_up = true;
                 report
             },
@@ -409,18 +401,18 @@ pub async fn input_integrity_benchmark() {
 #[allow(clippy::too_many_arguments)]
 #[embassy_executor::task]
 pub async fn update_button_state_task(
-    btn_z: Input<'static, AnyPin>,
-    btn_a: Input<'static, AnyPin>,
-    btn_b: Input<'static, AnyPin>,
-    btn_dright: Input<'static, AnyPin>,
-    btn_dup: Input<'static, AnyPin>,
-    btn_ddown: Input<'static, AnyPin>,
-    btn_dleft: Input<'static, AnyPin>,
-    btn_l: Input<'static, AnyPin>,
-    btn_r: Input<'static, AnyPin>,
-    btn_x: Input<'static, AnyPin>,
-    btn_y: Input<'static, AnyPin>,
-    btn_start: Input<'static, AnyPin>,
+    btn_z: Input<'static>,
+    btn_a: Input<'static>,
+    btn_b: Input<'static>,
+    btn_dright: Input<'static>,
+    btn_dup: Input<'static>,
+    btn_ddown: Input<'static>,
+    btn_dleft: Input<'static>,
+    btn_l: Input<'static>,
+    btn_r: Input<'static>,
+    btn_x: Input<'static>,
+    btn_y: Input<'static>,
+    btn_start: Input<'static>,
 ) {
     // upon loop entry, we check for the reset combo once
     if btn_a.is_low() && btn_x.is_low() && btn_y.is_low() {
@@ -431,6 +423,15 @@ pub async fn update_button_state_task(
         loop {}
     }
 
+    {
+        let mut m = MUTEX_CONTROLLER_MODE.lock().await;
+        *m = if btn_start.is_low() {
+            Some(ControllerMode::Procon)
+        } else {
+            Some(ControllerMode::GcAdapter)
+        };
+    }
+
     let input_consistency_mode = {
         while MUTEX_INPUT_CONSISTENCY_MODE.lock().await.is_none() {
             Timer::after(Duration::from_millis(100)).await;
@@ -438,9 +439,9 @@ pub async fn update_button_state_task(
         MUTEX_INPUT_CONSISTENCY_MODE.lock().await.unwrap()
     };
 
-    let mut previous_state = GcReport::default();
+    let mut previous_state = GcState::default();
 
-    let mut gcc_state = GcReport::default();
+    let mut gcc_state = GcState::default();
 
     let gcc_publisher = CHANNEL_GCC_STATE.publisher().unwrap();
 
@@ -546,8 +547,8 @@ pub async fn update_button_state_task(
 #[link_section = ".time_critical.update_stick_states_task"]
 pub async fn update_stick_states_task(
     spi: Spi<'static, SPI0, embassy_rp::spi::Blocking>,
-    spi_acs: Output<'static, AnyPin>,
-    spi_ccs: Output<'static, AnyPin>,
+    spi_acs: Output<'static>,
+    spi_ccs: Output<'static>,
 ) {
     Timer::after_secs(1).await;
     *SPI_SHARED.lock().await = Some(spi);
@@ -608,7 +609,7 @@ pub async fn update_stick_states_task(
             let n = Instant::now();
 
             match (n - last_loop_time).as_micros() {
-                a if a > 1666 => debug!("Loop took {} us", a),
+                a if a > 800 => debug!("Loop took {} us", a),
                 _ => {}
             };
             last_loop_time = n;
diff --git a/src/input_filter.rs b/src/input_filter.rs
index 3e000e8..7db05c4 100644
--- a/src/input_filter.rs
+++ b/src/input_filter.rs
@@ -2,7 +2,7 @@ use defmt::warn;
 
 use crate::{
     config::{is_awaitable_button_pressed, AwaitableButtons},
-    gcc_hid::GcReport,
+    usb_comms::GcState,
 };
 
 /**
@@ -14,7 +14,7 @@ use crate::{
  */
 
 pub trait InputFilter: Sized {
-    fn apply_filter(&mut self, gcc_state: &mut GcReport);
+    fn apply_filter(&mut self, gcc_state: &mut GcState);
 }
 
 /// Presses a single button if another button is pressed.
@@ -26,7 +26,7 @@ pub struct SingleButtonMacroFilter {
 }
 
 impl InputFilter for SingleButtonMacroFilter {
-    fn apply_filter(&mut self, gcc_state: &mut GcReport) {
+    fn apply_filter(&mut self, gcc_state: &mut GcState) {
         if is_awaitable_button_pressed(gcc_state, &self.btn_instigator) {
             match self.btn_to_press {
                 AwaitableButtons::A => {
@@ -83,7 +83,7 @@ impl InputFilter for SingleButtonMacroFilter {
 pub struct CStickUpTiltFilter;
 
 impl InputFilter for CStickUpTiltFilter {
-    fn apply_filter(&mut self, gcc_state: &mut GcReport) {
+    fn apply_filter(&mut self, gcc_state: &mut GcState) {
         if gcc_state.cstick_y > 157 {
             if (137..=201).contains(&gcc_state.cstick_x) {
                 gcc_state.cstick_x = 201;
@@ -110,7 +110,7 @@ impl InputFilter for CStickUpTiltFilter {
 pub struct CStickAngledFTiltFilter;
 
 impl InputFilter for CStickAngledFTiltFilter {
-    fn apply_filter(&mut self, gcc_state: &mut GcReport) {
+    fn apply_filter(&mut self, gcc_state: &mut GcState) {
         if gcc_state.cstick_y > 147 {
             if (147..=225).contains(&gcc_state.cstick_x) {
                 gcc_state.cstick_x = 205;
@@ -136,5 +136,5 @@ impl InputFilter for CStickAngledFTiltFilter {
 pub struct DummyFilter;
 
 impl InputFilter for DummyFilter {
-    fn apply_filter(&mut self, _gcc_state: &mut GcReport) {}
+    fn apply_filter(&mut self, _gcc_state: &mut GcState) {}
 }
diff --git a/src/main.rs b/src/main.rs
index 244f920..505abc5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,19 +1,16 @@
-//! This example test the RP Pico on board LED.
-//!
-//! It does not work with the RP Pico W board. See wifi_blinky.rs.
-
 #![no_std]
 #![no_main]
 mod config;
 mod filter;
-mod gcc_hid;
 mod helpers;
 mod input;
 mod input_filter;
+mod procon_hid;
 mod stick;
+mod usb_comms;
 
 use config::config_task;
-use defmt::{debug, info};
+use defmt::info;
 use embassy_executor::Executor;
 use embassy_rp::{
     bind_interrupts,
@@ -24,14 +21,14 @@ use embassy_rp::{
     spi::{self, Spi},
     usb::{Driver, InterruptHandler},
 };
-use gcc_hid::usb_transfer_task;
 use gpio::{Level, Output};
+use usb_comms::usb_transfer_task;
 
 use input::{update_button_state_task, update_stick_states_task};
 use static_cell::StaticCell;
 
 use crate::config::enter_config_mode_task;
-use crate::gcc_hid::rumble_task;
+use crate::usb_comms::rumble_task;
 
 use {defmt_rtt as _, panic_probe as _};
 
@@ -77,12 +74,11 @@ fn main() -> ! {
 
     spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || {
         let executor1 = EXECUTOR1.init(Executor::new());
-        debug!("Mana");
         executor1.run(|spawner| {
             spawner.spawn(usb_transfer_task(uid, driver)).unwrap();
             spawner.spawn(enter_config_mode_task()).unwrap();
             spawner
-                .spawn(rumble_task(p.PIN_25, p.PIN_29, p.PWM_CH4, p.PWM_CH6))
+                .spawn(rumble_task(p.PIN_25, p.PIN_29, p.PWM_SLICE4, p.PWM_SLICE6))
                 .unwrap();
             // spawner.spawn(input_integrity_benchmark()).unwrap();
             spawner
diff --git a/src/procon_hid.rs b/src/procon_hid.rs
new file mode 100644
index 0000000..676a323
--- /dev/null
+++ b/src/procon_hid.rs
@@ -0,0 +1,621 @@
+///
+/// The majority of the logic in this file is derived from HOJA-LIB-RP2040
+/// https://github.com/HandHeldLegend/HOJA-LIB-RP2040
+///
+/// The original author gave their consent for this to be included in the NaxGCC firmware,
+/// and for this file to be distributed under the GPLv3, see https://git.naxdy.org/NaxdyOrg/NaxGCC-FW/pulls/26
+///
+use core::ops::{Deref, DerefMut};
+
+use defmt::{info, trace, Format};
+use embassy_rp::clocks::RoscRng;
+use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
+use embassy_time::Instant;
+use embassy_usb::{
+    class::hid::{ReportId, RequestHandler},
+    control::OutResponse,
+};
+use packed_struct::{derive::PackedStruct, PackedStruct};
+use rand::RngCore;
+
+use crate::usb_comms::{GcState, HidReportBuilder};
+
+const SW_INFO_SET_MAC: u8 = 0x01;
+
+const SW_CMD_SET_INPUT_MODE: u8 = 0x03;
+const SW_CMD_GET_DEVINFO: u8 = 0x02;
+const SW_CMD_SET_SHIPMODE: u8 = 0x08;
+const SW_CMD_GET_SPI: u8 = 0x10;
+const SW_CMD_SET_PAIRING: u8 = 0x01;
+const SW_CMD_GET_TRIGGERET: u8 = 0x04;
+
+const ACK_GET_DEVINFO: u8 = 0x82;
+const ACK_GET_SPI: u8 = 0x90;
+const ACK_SET_PAIRING: u8 = 0x81;
+const ACK_GET_TRIGERET: u8 = 0x83;
+
+const ACK_GENERIC: u8 = 0x80;
+
+const RM_SEND_STATE: u8 = 0x30;
+
+const PRO_CONTROLLER_STRING: [u8; 24] = [
+    0x00, 0x25, 0x08, 0x50, 0x72, 0x6F, 0x20, 0x43, 0x6F, 0x6E, 0x74, 0x72, 0x6F, 0x6C, 0x6C, 0x65,
+    0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68,
+];
+
+#[derive(Debug, Format, Clone, Copy)]
+struct ProconRequestInfo {
+    report_id: ProconRequestId,
+    response_id: ProconResponseId,
+    command_id: u8,
+    raw_data: [u8; 64],
+}
+
+#[derive(Debug, Format, Clone, Copy)]
+enum ProconRequestId {
+    GetInfo = 0x80,
+    Command = 0x01,
+    Rumble = 0x10,
+}
+
+#[derive(Debug, Format, Clone, Copy)]
+enum ProconResponseId {
+    GetInfo = 0x81,
+    GetState = 0x30,
+    Command = 0x21,
+}
+
+static SIGNAL_PROCON_REQUEST: Signal<CriticalSectionRawMutex, ProconRequestInfo> = Signal::new();
+
+#[rustfmt::skip]
+pub const PROCON_REPORT_DESCRIPTOR: &[u8] = &[
+    0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+    0x15, 0x00, // Logical Minimum (0)
+
+    0x09, 0x04, // Usage (Joystick)
+    0xA1, 0x01, // Collection (Application)
+
+    0x85, 0x30, //   Report ID (48)
+    0x05, 0x01, //   Usage Page (Generic Desktop Ctrls)
+    0x05, 0x09, //   Usage Page (Button)
+    0x19, 0x01, //   Usage Minimum (0x01)
+    0x29, 0x0A, //   Usage Maximum (0x0A)
+    0x15, 0x00, //   Logical Minimum (0)
+    0x25, 0x01, //   Logical Maximum (1)
+    0x75, 0x01, //   Report Size (1)
+    0x95, 0x0A, //   Report Count (10)
+    0x55, 0x00, //   Unit Exponent (0)
+    0x65, 0x00, //   Unit (None)
+    0x81, 0x02, //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x05, 0x09, //   Usage Page (Button)
+    0x19, 0x0B, //   Usage Minimum (0x0B)
+    0x29, 0x0E, //   Usage Maximum (0x0E)
+    0x15, 0x00, //   Logical Minimum (0)
+    0x25, 0x01, //   Logical Maximum (1)
+    0x75, 0x01, //   Report Size (1)
+    0x95, 0x04, //   Report Count (4)
+    0x81, 0x02, //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x75, 0x01, //   Report Size (1)
+    0x95, 0x02, //   Report Count (2)
+    0x81, 0x03, //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+
+    0x0B, 0x01, 0x00, 0x01, 0x00, //   Usage (0x010001)
+    0xA1, 0x00,                   //   Collection (Physical)
+    0x0B, 0x30, 0x00, 0x01, 0x00, //     Usage (0x010030)
+    0x0B, 0x31, 0x00, 0x01, 0x00, //     Usage (0x010031)
+    0x0B, 0x32, 0x00, 0x01, 0x00, //     Usage (0x010032)
+    0x0B, 0x35, 0x00, 0x01, 0x00, //     Usage (0x010035)
+    0x15, 0x00,                   //     Logical Minimum (0)
+    0x27, 0xFF, 0xFF, 0x00, 0x00, //     Logical Maximum (65534)
+    0x75, 0x10,                   //     Report Size (16)
+    0x95, 0x04,                   //     Report Count (4)
+    0x81, 0x02,                   //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,                         //   End Collection
+
+    0x0B, 0x39, 0x00, 0x01, 0x00, //   Usage (0x010039)
+    0x15, 0x00,                   //   Logical Minimum (0)
+    0x25, 0x07,                   //   Logical Maximum (7)
+    0x35, 0x00,                   //   Physical Minimum (0)
+    0x46, 0x3B, 0x01,             //   Physical Maximum (315)
+    0x65, 0x14,                   //   Unit (System: English Rotation, Length: Centimeter)
+    0x75, 0x04,                   //   Report Size (4)
+    0x95, 0x01,                   //   Report Count (1)
+    0x81, 0x02,                   //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x05, 0x09,                   //   Usage Page (Button)
+    0x19, 0x0F,                   //   Usage Minimum (0x0F)
+    0x29, 0x12,                   //   Usage Maximum (0x12)
+    0x15, 0x00,                   //   Logical Minimum (0)
+    0x25, 0x01,                   //   Logical Maximum (1)
+    0x75, 0x01,                   //   Report Size (1)
+    0x95, 0x04,                   //   Report Count (4)
+    0x81, 0x02,                   //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x75, 0x08,                   //   Report Size (8)
+    0x95, 0x34,                   //   Report Count (52)
+    0x81, 0x03,                   //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+
+    0x06, 0x00, 0xFF, //   Usage Page (Vendor Defined 0xFF00)
+    0x85, 0x21,       //   Report ID (33)
+    0x09, 0x01,       //   Usage (0x01)
+    0x75, 0x08,       //   Report Size (8)
+    0x95, 0x3F,       //   Report Count (63)
+    0x81, 0x03,       //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+
+    0x85, 0x81, //   Report ID (-127)
+    0x09, 0x02, //   Usage (0x02)
+    0x75, 0x08, //   Report Size (8)
+    0x95, 0x3F, //   Report Count (63)
+    0x81, 0x03, //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+
+    0x85, 0x01, //   Report ID (1)
+    0x09, 0x03, //   Usage (0x03)
+    0x75, 0x08, //   Report Size (8)
+    0x95, 0x3F, //   Report Count (63)
+    0x91, 0x83, //   Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
+
+    0x85, 0x10, //   Report ID (16)
+    0x09, 0x04, //   Usage (0x04)
+    0x75, 0x08, //   Report Size (8)
+    0x95, 0x3F, //   Report Count (63)
+    0x91, 0x83, //   Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
+
+    0x85, 0x80, //   Report ID (-128)
+    0x09, 0x05, //   Usage (0x05)
+    0x75, 0x08, //   Report Size (8)
+    0x95, 0x3F, //   Report Count (63)
+    0x91, 0x83, //   Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
+
+    0x85, 0x82, //   Report ID (-126)
+    0x09, 0x06, //   Usage (0x06)
+    0x75, 0x08, //   Report Size (8)
+    0x95, 0x3F, //   Report Count (63)
+    0x91, 0x83, //   Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
+
+    0xC0, // End Collection
+
+    // 203 bytes
+];
+
+#[derive(Clone, Copy, Debug, Format)]
+struct ProconByteReport([u8; 64]);
+
+impl Deref for ProconByteReport {
+    type Target = [u8; 64];
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for ProconByteReport {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl ProconByteReport {
+    fn set_report_id(&mut self, id: u8) {
+        self[0] = id;
+    }
+
+    fn set_battery_status(&mut self) {
+        self[2] = BatteryStatus::default()
+            .pack()
+            .expect("Failed to pack fake procon battery status")[0];
+    }
+
+    fn set_timer(&mut self) {
+        self[1] = Instant::now().as_millis() as u8;
+    }
+
+    fn set_ack(&mut self, ack: u8) {
+        self[13] = ack;
+    }
+
+    fn set_subcommand(&mut self, cmd: u8) {
+        self[14] = cmd;
+    }
+
+    fn set_devinfo(&mut self) {
+        self[15] = 0x04; // NS Firmware primary   (4.x)
+        self[16] = 0x33; // NS Firmware secondary (x.21)
+
+        self[17] = 0x03; // Controller ID primary (Pro Controller)
+        self[18] = 0x02; // Controller ID secondary
+
+        self[25] = 0x01;
+        self[26] = 0x02;
+    }
+
+    fn sw_spi_readfromaddress(&mut self, offset_address: u8, address: u8, length: u8) {
+        let read_info = [address, offset_address, 0x00, 0x00, length];
+        self[15..(15 + read_info.len())].copy_from_slice(&read_info);
+
+        let mut output_spi_data = [0u8; 30];
+
+        output_spi_data
+            .iter_mut()
+            .enumerate()
+            .for_each(|(i, e)| *e = sw_spi_getaddressdata(offset_address, address + i as u8));
+
+        self[19..(19 + length as usize)].copy_from_slice(&output_spi_data[..(length as usize)]);
+    }
+
+    fn set_trigerret(&mut self, time_10_ms: u16) {
+        let [upper_ms, lower_ms] = time_10_ms.to_be_bytes();
+
+        for i in 0..14 {
+            self[15 + i] = upper_ms;
+            self[16 + i] = lower_ms;
+        }
+    }
+}
+
+fn sw_spi_getaddressdata(_offset_address: u8, _address: u8) -> u8 {
+    // TODO
+    0
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
+#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
+pub struct ProconButtonsRight {
+    #[packed_field(bits = "0")]
+    pub button_y: bool,
+    #[packed_field(bits = "1")]
+    pub button_x: bool,
+    #[packed_field(bits = "2")]
+    pub button_b: bool,
+    #[packed_field(bits = "3")]
+    pub button_a: bool,
+    #[packed_field(bits = "4")]
+    pub trigger_r_sr: bool,
+    #[packed_field(bits = "5")]
+    pub trigger_r_sl: bool,
+    #[packed_field(bits = "6")]
+    pub trigger_r: bool,
+    #[packed_field(bits = "7")]
+    pub trigger_zr: bool,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
+#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
+pub struct ProconButtonsShared {
+    #[packed_field(bits = "0")]
+    pub button_minus: bool,
+    #[packed_field(bits = "1")]
+    pub button_plus: bool,
+    #[packed_field(bits = "2")]
+    pub button_sb_right: bool,
+    #[packed_field(bits = "3")]
+    pub button_sb_left: bool,
+    #[packed_field(bits = "4")]
+    pub button_home: bool,
+    #[packed_field(bits = "5")]
+    pub button_capture: bool,
+    #[packed_field(bits = "6")]
+    pub none: bool,
+    #[packed_field(bits = "7")]
+    pub change_grip_active: bool,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
+#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
+pub struct ProconButtonsLeft {
+    #[packed_field(bits = "0")]
+    pub dpad_down: bool,
+    #[packed_field(bits = "1")]
+    pub dpad_up: bool,
+    #[packed_field(bits = "2")]
+    pub dpad_right: bool,
+    #[packed_field(bits = "3")]
+    pub dped_left: bool,
+    #[packed_field(bits = "4")]
+    pub trigger_l_sr: bool,
+    #[packed_field(bits = "5")]
+    pub trigger_l_sl: bool,
+    #[packed_field(bits = "6")]
+    pub trigger_l: bool,
+    #[packed_field(bits = "7")]
+    pub trigger_zl: bool,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
+#[packed_struct(bit_numbering = "msb0", endian = "msb", size_bytes = "9")]
+pub struct ProconState {
+    #[packed_field(bits = "0..=7")]
+    pub buttons_right: ProconButtonsRight,
+    #[packed_field(bits = "8..=15")]
+    pub buttons_shared: ProconButtonsShared,
+    #[packed_field(bits = "16..=23")]
+    pub buttons_left: ProconButtonsLeft,
+    #[packed_field(bits = "24..=39")]
+    pub lstick_x: u16,
+    #[packed_field(bits = "40..=47")]
+    pub lstick_y: u8,
+    #[packed_field(bits = "48..=60")]
+    pub rstick_x: u16,
+    #[packed_field(bits = "64..=71")]
+    pub rstick_y: u8,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PackedStruct, Format)]
+#[packed_struct(bit_numbering = "lsb0", endian = "msb", size_bytes = "1")]
+struct BatteryStatus {
+    #[packed_field(bits = "0..=3")]
+    connection: u8,
+    #[packed_field(bits = "4..=7")]
+    battery_level: u8,
+}
+
+impl Default for BatteryStatus {
+    fn default() -> Self {
+        Self {
+            connection: 1,
+            battery_level: 8,
+        }
+    }
+}
+
+impl From<&GcState> for ProconState {
+    fn from(value: &GcState) -> Self {
+        Self {
+            buttons_left: ProconButtonsLeft {
+                dpad_down: value.buttons_1.dpad_down,
+                dpad_right: value.buttons_1.dpad_right,
+                dpad_up: value.buttons_1.dpad_up,
+                dped_left: value.buttons_1.dpad_left,
+                trigger_l: value.buttons_2.button_l,
+                trigger_zl: value.buttons_2.button_l,
+                ..Default::default()
+            },
+            buttons_right: ProconButtonsRight {
+                button_a: value.buttons_1.button_a,
+                button_b: value.buttons_1.button_b,
+                button_x: value.buttons_1.button_x,
+                button_y: value.buttons_1.button_y,
+                trigger_r: value.buttons_2.button_r,
+                trigger_zr: value.buttons_2.button_z,
+                ..Default::default()
+            },
+            buttons_shared: ProconButtonsShared {
+                button_plus: value.buttons_2.button_start && !value.buttons_2.button_z,
+                button_home: value.buttons_2.button_start && value.buttons_2.button_z,
+                ..Default::default()
+            },
+            lstick_x: value.stick_x as u16 * 257,
+            lstick_y: value.stick_y,
+            rstick_x: value.cstick_x as u16 * 257,
+            rstick_y: value.cstick_y,
+        }
+    }
+}
+
+pub struct ProconReportBuilder {
+    switch_reporting_mode: u8,
+    switch_mac_address: [u8; 6],
+    switch_ltk: [u8; 16],
+}
+
+impl Default for ProconReportBuilder {
+    fn default() -> Self {
+        Self {
+            switch_reporting_mode: 0,
+            switch_mac_address: [0u8; 6],
+            switch_ltk: gen_switch_ltk(),
+        }
+    }
+}
+
+fn gen_switch_ltk() -> [u8; 16] {
+    let mut switch_ltk = [0u8; 16];
+
+    switch_ltk.iter_mut().for_each(|e| {
+        *e = RoscRng.next_u64() as u8;
+    });
+
+    switch_ltk
+}
+
+impl ProconReportBuilder {
+    fn get_info_report(&self, current_report_info: &ProconRequestInfo) -> [u8; 64] {
+        let mut report = ProconByteReport([0u8; 64]);
+
+        report.set_report_id(ProconResponseId::GetInfo as u8);
+
+        if current_report_info.command_id == SW_INFO_SET_MAC {
+            report[1] = SW_INFO_SET_MAC;
+            report[3] = 0x03;
+
+            self.switch_mac_address
+                .iter()
+                .rev()
+                .enumerate()
+                .for_each(|(i, e)| {
+                    report[4 + i] = *e;
+                });
+        } else {
+            report[1] = current_report_info.command_id;
+        }
+
+        *report
+    }
+
+    fn get_state_report(&self, state: &ProconState) -> [u8; 64] {
+        static mut UNKNOWN: u8 = 0xA;
+
+        unsafe {
+            UNKNOWN = match UNKNOWN {
+                0xA => 0xB,
+                0xB => 0xC,
+                _ => 0xA,
+            };
+        }
+
+        let mut report = ProconByteReport([0u8; 64]);
+
+        let data = state
+            .pack()
+            .expect("Failed to pack pro controller input data");
+
+        report.set_report_id(ProconResponseId::GetState as u8);
+        report.set_timer();
+        report.set_battery_status();
+
+        report[3..=11].copy_from_slice(&data);
+        report[12] = unsafe { UNKNOWN };
+
+        *report
+    }
+
+    fn get_command_report(&mut self, current_report_info: &ProconRequestInfo) -> [u8; 64] {
+        let mut report = ProconByteReport([0u8; 64]);
+
+        report.set_report_id(ProconResponseId::Command as u8);
+        report.set_timer();
+        report.set_battery_status();
+        report.set_subcommand(current_report_info.command_id);
+
+        match current_report_info.command_id {
+            SW_CMD_SET_INPUT_MODE => {
+                report.set_ack(ACK_GENERIC);
+                self.switch_reporting_mode = current_report_info.raw_data[11];
+                info!(
+                    "Switch reporting mode is now {:x}",
+                    self.switch_reporting_mode
+                );
+            }
+            SW_CMD_GET_DEVINFO => {
+                report.set_ack(ACK_GET_DEVINFO);
+                report.set_devinfo();
+            }
+            SW_CMD_GET_SPI => {
+                report.set_ack(ACK_GET_SPI);
+                report.sw_spi_readfromaddress(
+                    current_report_info.raw_data[12],
+                    current_report_info.raw_data[11],
+                    current_report_info.raw_data[15],
+                );
+            }
+            SW_CMD_SET_SHIPMODE => {
+                report.set_ack(ACK_GENERIC);
+            }
+            SW_CMD_SET_PAIRING => {
+                report.set_ack(ACK_SET_PAIRING);
+                self.perform_pairing(&mut report, current_report_info);
+            }
+            SW_CMD_GET_TRIGGERET => {
+                report.set_ack(ACK_GET_TRIGERET);
+                report.set_trigerret(100);
+            }
+            _ => {
+                report.set_ack(ACK_GENERIC);
+            }
+        }
+
+        *report
+    }
+
+    fn perform_pairing(
+        &mut self,
+        report: &mut ProconByteReport,
+        current_report_info: &ProconRequestInfo,
+    ) {
+        let pairing_phase = current_report_info.raw_data[11];
+        let host_address = &current_report_info.raw_data[12..];
+
+        match pairing_phase {
+            1 => {
+                self.switch_mac_address
+                    .iter_mut()
+                    .enumerate()
+                    .for_each(|(i, e)| *e = host_address[5 - i]);
+
+                self.switch_mac_address
+                    .iter()
+                    .rev()
+                    .enumerate()
+                    .for_each(|(i, e)| {
+                        report[16 + i] = *e;
+                    });
+
+                report[22..(22 + PRO_CONTROLLER_STRING.len())]
+                    .copy_from_slice(&PRO_CONTROLLER_STRING);
+            }
+            2 => {
+                report[15] = 2;
+                report[16..(16 + self.switch_ltk.len())].copy_from_slice(&self.switch_ltk);
+            }
+            3 => {
+                report[15] = 3;
+            }
+            _ => {}
+        }
+    }
+}
+
+impl HidReportBuilder<64> for ProconReportBuilder {
+    async fn get_hid_report(&mut self, state: &GcState) -> [u8; 64] {
+        let current_report_info = if self.switch_reporting_mode == RM_SEND_STATE {
+            SIGNAL_PROCON_REQUEST.try_take()
+        } else {
+            Some(SIGNAL_PROCON_REQUEST.wait().await)
+        };
+
+        if let Some(current_report_info) = current_report_info {
+            match current_report_info.report_id {
+                ProconRequestId::GetInfo => self.get_info_report(&current_report_info),
+                ProconRequestId::Command => self.get_command_report(&current_report_info),
+                ProconRequestId::Rumble => self.get_state_report(&ProconState::from(state)),
+            }
+        } else {
+            self.get_state_report(&ProconState::from(state))
+        }
+    }
+}
+
+pub struct ProconRequestHandler;
+
+impl RequestHandler for ProconRequestHandler {
+    fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
+        info!("Get report for {:?}", id);
+        None
+    }
+
+    fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse {
+        trace!("Set report for {:?}: {:x}", id, data);
+
+        let mut buf = [0u8; 64];
+        let len_to_copy = buf.len().min(data.len());
+        buf[..len_to_copy].copy_from_slice(&data[..len_to_copy]);
+
+        if let ReportId::Out(id) = id {
+            if id == ProconRequestId::GetInfo as u8 {
+                SIGNAL_PROCON_REQUEST.signal(ProconRequestInfo {
+                    command_id: buf[1],
+                    report_id: ProconRequestId::GetInfo,
+                    response_id: ProconResponseId::GetInfo,
+                    raw_data: buf,
+                });
+            } else if id == ProconRequestId::Command as u8 {
+                SIGNAL_PROCON_REQUEST.signal(ProconRequestInfo {
+                    command_id: buf[10],
+                    report_id: ProconRequestId::Command,
+                    response_id: ProconResponseId::Command,
+                    raw_data: buf,
+                });
+            } else if id == ProconRequestId::Rumble as u8 {
+                // TODO: handle rumble
+            }
+        }
+
+        OutResponse::Accepted
+    }
+
+    fn set_idle_ms(&mut self, id: Option<ReportId>, dur: u32) {
+        info!("Set idle rate for {:?} to {:?}", id, dur);
+    }
+
+    fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> {
+        info!("Get idle rate for {:?}", id);
+        None
+    }
+}
diff --git a/src/gcc_hid.rs b/src/usb_comms.rs
similarity index 82%
rename from src/gcc_hid.rs
rename to src/usb_comms.rs
index 23edcdf..91f40a6 100644
--- a/src/gcc_hid.rs
+++ b/src/usb_comms.rs
@@ -7,7 +7,7 @@ use core::{default::Default, future::Future};
 use defmt::{debug, info, trace, warn, Format};
 use embassy_futures::join::join;
 use embassy_rp::{
-    peripherals::{PIN_25, PIN_29, PWM_CH4, PWM_CH6, USB},
+    peripherals::{PIN_25, PIN_29, PWM_SLICE4, PWM_SLICE6, USB},
     pwm::Pwm,
     usb::Driver as EmbassyDriver,
 };
@@ -27,6 +27,7 @@ use packed_struct::{derive::PackedStruct, PackedStruct};
 use crate::{
     config::{ControllerMode, InputConsistencyMode},
     input::CHANNEL_GCC_STATE,
+    procon_hid::{ProconReportBuilder, ProconRequestHandler, PROCON_REPORT_DESCRIPTOR},
 };
 
 static SIGNAL_RUMBLE: Signal<CriticalSectionRawMutex, bool> = Signal::new();
@@ -105,8 +106,8 @@ pub const GCC_REPORT_DESCRIPTOR: &[u8] = &[
     0xC0,              // End Collection
 ];
 
-trait HidReportable<const LEN: usize> {
-    fn get_hid_report(&self) -> [u8; LEN];
+pub trait HidReportBuilder<const LEN: usize> {
+    async fn get_hid_report(&mut self, state: &GcState) -> [u8; LEN];
 }
 
 struct UsbConfig {
@@ -153,7 +154,7 @@ pub struct GcButtons2 {
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PackedStruct, Format)]
 #[packed_struct(bit_numbering = "msb0", size_bytes = "8")]
-pub struct GcReport {
+pub struct GcState {
     #[packed_field(bits = "0..=7")]
     pub buttons_1: GcButtons1,
     #[packed_field(bits = "8..=15")]
@@ -172,7 +173,7 @@ pub struct GcReport {
     pub trigger_r: u8,
 }
 
-impl Default for GcReport {
+impl Default for GcState {
     fn default() -> Self {
         Self {
             buttons_1: GcButtons1::default(),
@@ -187,50 +188,58 @@ impl Default for GcReport {
     }
 }
 
-impl HidReportable<37> for GcReport {
-    fn get_hid_report(&self) -> [u8; 37] {
-        static mut GC_FIRST: bool = false;
+#[derive(Default)]
+struct GcReportBuilder {
+    gc_first: bool,
+}
 
+impl HidReportBuilder<37> for GcReportBuilder {
+    async fn get_hid_report(&mut self, state: &GcState) -> [u8; 37] {
         let mut buffer = [0u8; 37];
 
         buffer[0] = 0x21;
         buffer[1] |= 0x14;
 
-        let data = self.pack().expect("Failed to pack GC input data");
+        let data = state.pack().expect("Failed to pack GC input data");
 
-        if unsafe { !GC_FIRST } {
+        if !self.gc_first {
             buffer[1] |= 0x04;
             buffer[10] |= 0x04;
             buffer[19] |= 0x04;
             buffer[28] |= 0x04;
-            unsafe { GC_FIRST = true };
+            self.gc_first = true;
         } else {
             // controller in "port 1"
-            buffer[2..=9].copy_from_slice(&data[0..=7]);
+            buffer[2..=9].copy_from_slice(&data);
         }
 
         buffer
     }
 }
 
-struct GccRequestHandler {}
+struct GccRequestHandler;
 
 impl RequestHandler for GccRequestHandler {
-    fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
+    fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
         info!("Get report for {:?}", id);
         None
     }
 
-    fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse {
-        info!("Set report for {:?}: {=[u8]}", id, data);
+    fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse {
+        trace!("Set report for {:?}: {:x}", id, data);
+
+        if data.len() > 1 {
+            SIGNAL_RUMBLE.signal((data[1] & 0x01) != 0);
+        }
+
         OutResponse::Accepted
     }
 
-    fn set_idle_ms(&self, id: Option<ReportId>, dur: u32) {
+    fn set_idle_ms(&mut self, id: Option<ReportId>, dur: u32) {
         info!("Set idle rate for {:?} to {:?}", id, dur);
     }
 
-    fn get_idle_ms(&self, id: Option<ReportId>) -> Option<u32> {
+    fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> {
         info!("Get idle rate for {:?}", id);
         None
     }
@@ -281,13 +290,12 @@ impl Handler for MyDeviceHandler {
 fn mk_hid_reader_writer<'d, D: Driver<'d>, const R: usize, const W: usize>(
     input_consistency_mode: InputConsistencyMode,
     report_descriptor: &'d [u8],
-    request_handler: &'d GccRequestHandler,
     mut builder: Builder<'d, D>,
     state: &'d mut State<'d>,
 ) -> (UsbDevice<'d, D>, HidReader<'d, D, R>, HidWriter<'d, D, W>) {
     let hid_config = embassy_usb::class::hid::Config {
         report_descriptor,
-        request_handler: Some(request_handler),
+        request_handler: None,
         poll_ms: match input_consistency_mode {
             InputConsistencyMode::Original => 8,
             InputConsistencyMode::ConsistencyHack
@@ -308,13 +316,13 @@ fn mk_hid_reader_writer<'d, D: Driver<'d>, const R: usize, const W: usize>(
     (usb, reader, writer)
 }
 
-fn mk_usb_transfer_futures<'d, D, F, H, const R: usize, const W: usize>(
+fn mk_usb_transfer_futures<'d, D, H, Rq, const R: usize, const W: usize>(
     input_consistency_mode: InputConsistencyMode,
-    usb_config: UsbConfig,
-    request_handler: &'d GccRequestHandler,
+    usb_config: &UsbConfig,
+    request_handler: &'d mut Rq,
     builder: Builder<'d, D>,
     state: &'d mut State<'d>,
-    preprocess_report: F,
+    mut hid_report_builder: H,
 ) -> (
     impl Future<Output = ()> + 'd,
     impl Future<Output = ()> + 'd,
@@ -322,13 +330,12 @@ fn mk_usb_transfer_futures<'d, D, F, H, const R: usize, const W: usize>(
 )
 where
     D: Driver<'d> + 'd,
-    F: Fn(GcReport) -> H + 'd,
-    H: HidReportable<W>,
+    H: HidReportBuilder<W> + 'd,
+    Rq: RequestHandler,
 {
-    let (mut usb, mut reader, mut writer) = mk_hid_reader_writer::<_, R, W>(
+    let (mut usb, reader, mut writer) = mk_hid_reader_writer::<_, R, W>(
         input_consistency_mode,
         usb_config.report_descriptor,
-        request_handler,
         builder,
         state,
     );
@@ -368,9 +375,9 @@ where
 
             writer.ready().await;
 
-            let state = preprocess_report(gcc_subscriber.next_message_pure().await);
+            let state = gcc_subscriber.next_message_pure().await;
 
-            let report = state.get_hid_report();
+            let report = hid_report_builder.get_hid_report(&state).await;
 
             trace!("Writing report: {:08b}", report);
 
@@ -395,19 +402,8 @@ where
     };
 
     let out_fut = async move {
-        loop {
-            trace!("Readery loop");
-            let mut buf = [0u8; R];
-            match reader.read(&mut buf).await {
-                Ok(_e) => {
-                    debug!("READ SOMETHIN: {:08b}", buf);
-                    SIGNAL_RUMBLE.signal((buf[1] & 0x01) != 0);
-                }
-                Err(e) => {
-                    warn!("Failed to read: {:?}", e);
-                }
-            }
-        }
+        trace!("Readery loop");
+        reader.run(true, request_handler).await;
     };
 
     let usb_fut_wrapped = async {
@@ -420,12 +416,6 @@ where
 
 #[embassy_executor::task]
 pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'static, USB>) {
-    let config = UsbConfig {
-        vid: 0x057e,
-        pid: 0x0337,
-        report_descriptor: GCC_REPORT_DESCRIPTOR,
-    };
-
     let input_consistency_mode = {
         while MUTEX_INPUT_CONSISTENCY_MODE.lock().await.is_none() {
             Timer::after(Duration::from_millis(100)).await;
@@ -433,6 +423,27 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'stati
         MUTEX_INPUT_CONSISTENCY_MODE.lock().await.unwrap()
     };
 
+    let controller_mode = {
+        while MUTEX_CONTROLLER_MODE.lock().await.is_none() {
+            Timer::after(Duration::from_millis(100)).await;
+        }
+
+        MUTEX_CONTROLLER_MODE.lock().await.unwrap()
+    };
+
+    let config = match controller_mode {
+        ControllerMode::GcAdapter => UsbConfig {
+            vid: 0x057e,
+            pid: 0x0337,
+            report_descriptor: GCC_REPORT_DESCRIPTOR,
+        },
+        ControllerMode::Procon => UsbConfig {
+            vid: 0x57e,
+            pid: 0x2009,
+            report_descriptor: PROCON_REPORT_DESCRIPTOR,
+        },
+    };
+
     let mut serial_buffer = [0u8; 64];
 
     let serial = format_no_std::show(
@@ -471,13 +482,11 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'stati
     usb_config.device_sub_class = 0;
     usb_config.supports_remote_wakeup = true;
 
-    let mut device_descriptor = [0; 256];
     let mut config_descriptor = [0; 256];
     let mut bos_descriptor = [0; 256];
     let mut msos_descriptor = [0; 256];
     let mut control_buf = [0; 64];
 
-    let request_handler = GccRequestHandler {};
     let mut device_handler = MyDeviceHandler::new();
 
     let mut state = State::new();
@@ -485,7 +494,6 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'stati
     let mut builder = Builder::new(
         driver,
         usb_config,
-        &mut device_descriptor,
         &mut config_descriptor,
         &mut bos_descriptor,
         &mut msos_descriptor,
@@ -503,16 +511,36 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'stati
 
     builder.handler(&mut device_handler);
 
-    let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures::<_, _, _, 5, 37>(
-        input_consistency_mode,
-        config,
-        &request_handler,
-        builder,
-        &mut state,
-        |state| state,
-    );
+    match controller_mode {
+        ControllerMode::GcAdapter => {
+            let mut request_handler = GccRequestHandler;
 
-    join(usb_fut_wrapped, join(in_fut, out_fut)).await;
+            let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures::<_, _, _, 5, 37>(
+                input_consistency_mode,
+                &config,
+                &mut request_handler,
+                builder,
+                &mut state,
+                GcReportBuilder::default(),
+            );
+
+            join(usb_fut_wrapped, join(in_fut, out_fut)).await;
+        }
+        ControllerMode::Procon => {
+            let mut request_handler = ProconRequestHandler;
+
+            let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures::<_, _, _, 64, 64>(
+                input_consistency_mode,
+                &config,
+                &mut request_handler,
+                builder,
+                &mut state,
+                ProconReportBuilder::default(),
+            );
+
+            join(usb_fut_wrapped, join(in_fut, out_fut)).await;
+        }
+    };
 }
 
 fn calc_rumble_power(strength: u8) -> u16 {
@@ -527,8 +555,8 @@ fn calc_rumble_power(strength: u8) -> u16 {
 pub async fn rumble_task(
     pin_rumble: PIN_25,
     pin_brake: PIN_29,
-    pwm_ch_rumble: PWM_CH4,
-    pwm_ch_brake: PWM_CH6,
+    pwm_ch_rumble: PWM_SLICE4,
+    pwm_ch_brake: PWM_SLICE6,
 ) {
     let mut rumble_config: embassy_rp::pwm::Config = Default::default();
     rumble_config.top = 255;