diff --git a/flake.nix b/flake.nix index 30517e1..31039e0 100644 --- a/flake.nix +++ b/flake.nix @@ -14,8 +14,9 @@ # Build all targets (qmk.json): qmk userspace-compile # Flash Mirage: qmk flash -kb mode/m256wh -km mirage # (put board in DFU: hold MO(1)+grave, or hold MO(1)+B) -# Flash NEO65: qmk flash -kb neo/neo65_wired_for_trimode -km via -# (put board in DFU: hold B while plugging in) +# Flash NEO65: qmk flash -kb neo/neo65_trimode -km default +# (put board in DFU: hold B while plugging in -- WB32 DFU, use +# wb32-dfu-updater_cli, NOT dfu-util; see justfile) { description = "aliou's QMK external userspace"; @@ -44,32 +45,39 @@ # Apply local patches on top of the fetched firmware. Patches live in # ./patches/ and adapt upstream QMK so external-userspace builds work # against this read-only firmware copy (no fork needed). + # + # 0001-tolerate-readonly-firmware-root-copy.patch + # makes firmware-root cp steps tolerant of the read-only nix store. + # 0002-add-edthu-wireless-support.patch + # vendored from edthu/qmk_firmware#wireless (see + # vendor/edthu-wireless/README.md). Adds the WB32<->CH582F UART + # transport stack + the NEO65 tri-mode keyboard. Purely additive + # (all new files), so it can't bitrot against upstream changes. + # Regenerate via `scripts/regen-wireless-patch.sh`. qmkFirmwarePatched = pkgs.applyPatches { src = qmkFirmwareSrc; - patches = [ ./patches/0001-tolerate-readonly-firmware-root-copy.patch ]; + patches = [ + ./patches/0001-tolerate-readonly-firmware-root-copy.patch + ./patches/0002-add-edthu-wireless-support.patch + ]; }; - # Custom keyboards we maintain that are NOT in upstream QMK (no fork, - # not part of external-userspace -- see note below). We layer them onto - # the patched firmware at build time so QMK's Python + make lookups - # (which run with cwd = firmware, due to script_qmk.py's chdir) find - # them as ordinary firmware keyboards: is_keyboard(), find_info_json(), - # rules_mk(), make KEYBOARD_PATH_*, list-keyboards, etc. all work - # unmodified. - # - # Entry = path under our repo's keyboards/ that has a keyboard.json. - customKeyboards = [ "neo/neo65" ]; - + # Custom keyboards we maintain that are NOT in upstream QMK go through + # patches/ (see vendor/ and scripts/regen-wireless-patch.sh), not this + # overlay -- that path is reserved for keymap-level overlays handled by + # QMK's external-userspace machinery, and the NEO65 is now a real + # firmware keyboard via 0002-add-edthu-wireless-support.patch. qmkFirmware = pkgs.runCommand "qmk-firmware-overlayed" { src = qmkFirmwarePatched; } '' cp -r "$src" "$out" chmod -R u+w "$out" - ${pkgs.lib.concatStringsSep "\n" (map (kb: '' - mkdir -p "$out/keyboards/${pkgs.lib.dirOf kb}" - cp -r "${./keyboards}/${kb}" "$out/keyboards/${kb}" - '') customKeyboards)} ''; in { + # Exposed so scripts/regen-wireless-patch.sh can resolve a pristine + # checkout at the pinned rev without a git clone: + # nix build .#qmkFirmwareSrc --no-link --print-out-paths + packages.qmkFirmwareSrc = qmkFirmwareSrc; + devShells.default = pkgs.mkShell { packages = with pkgs; [ qmk # QMK CLI (python, with deps) diff --git a/justfile b/justfile index 45e5d6b..5732e59 100644 --- a/justfile +++ b/justfile @@ -26,12 +26,12 @@ mirage-flash: qmk flash -kb mode/m256wh -km mirage neo: - qmk compile -kb neo/neo65 -km via + qmk compile -kb neo/neo65_trimode -km default # Flash the NEO65. Enter DFU first: hold B while plugging in. WB32 DFU; # uses wb32-dfu-updater_cli (not dfu-util). neo-flash: - wb32-dfu-updater_cli -D neo_neo65_via.bin -s 0x08000000 -R -w + wb32-dfu-updater_cli -D neo_neo65_trimode_default.bin -s 0x08000000 -R -w # --- housekeeping --------------------------------------------------------- diff --git a/keyboards/neo/neo65/config.h b/keyboards/neo/neo65/config.h deleted file mode 100644 index b38aaa5..0000000 --- a/keyboards/neo/neo65/config.h +++ /dev/null @@ -1,32 +0,0 @@ -/* Copyright (C) 2022 jonylee@hfd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -/* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */ -#define LOCKING_SUPPORT_ENABLE -/* Locking resynchronize hack */ -#define LOCKING_RESYNC_ENABLE - -#define RGB_TRIGGER_ON_KEYDOWN -/* SPI Config for spi flash*/ -#define SPI_DRIVER SPIDQ -#define SPI_SCK_PIN B3 -#define SPI_MOSI_PIN B5 -#define SPI_MISO_PIN B4 -#define SPI_MOSI_PAL_MODE 5 - -#define EXTERNAL_FLASH_SPI_SLAVE_SELECT_PIN C12 -#define WEAR_LEVELING_BACKING_SIZE (8 * 1024) diff --git a/keyboards/neo/neo65/halconf.h b/keyboards/neo/neo65/halconf.h deleted file mode 100644 index 2f64e65..0000000 --- a/keyboards/neo/neo65/halconf.h +++ /dev/null @@ -1,23 +0,0 @@ -/* Copyright (C) 2022 jonylee@hfd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#define HAL_USE_I2C TRUE -#define HAL_USE_SPI TRUE -#define SPI_USE_WAIT TRUE -#define SPI_SELECT_MODE SPI_SELECT_MODE_PAD - -#include_next diff --git a/keyboards/neo/neo65/keymaps/default/keymap.c b/keyboards/neo/neo65/keymaps/default/keymap.c deleted file mode 100644 index 8bf5176..0000000 --- a/keyboards/neo/neo65/keymaps/default/keymap.c +++ /dev/null @@ -1,96 +0,0 @@ -/* Copyright (C) 2022 jonylee@hfd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include QMK_KEYBOARD_H - -enum __layers { - WIN_B, - WIN_W, - WIN_FN, - MAC_B, - MAC_W, - MAC_FN -}; - -#define KC_TASK LGUI(KC_TAB) -#define KC_FLXP LGUI(KC_E) - -// clang-format off -const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { - - [WIN_B] = LAYOUT_all( /* Base */ - KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_DEL, KC_MUTE, - KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS,KC_EQL, KC_BSPC, KC_HOME, - KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC,KC_RBRC, KC_BSLS, KC_PGUP, - KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT,KC_NUHS, KC_ENT, KC_PGDN, - KC_LSFT, KC_NUBS, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP, KC_END, - KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, KC_RALT, MO(WIN_FN),KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT), - - [WIN_W] = LAYOUT_all( /* Base */ - _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, _______, KC_UP, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, KC_LEFT, KC_DOWN, KC_RGHT, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_W, _______, - _______, _______, _______, _______, _______, MO(WIN_FN), _______, KC_A, KC_S, KC_D), - - [WIN_FN] = LAYOUT_all( /* WASD/�������� */ - _______, KC_MYCM, KC_MAIL, KC_WSCH, KC_WHOM, KC_MSEL, KC_MPLY, KC_MPRV, KC_MNXT, _______, _______, _______, _______, _______, RGB_MOD, - _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, RGB_SPD, RGB_SPI, _______, _______, - _______, _______,TG(WIN_W),_______, _______, _______, _______, _______, KC_INS, DF(MAC_B),KC_PSCR,_______, _______, RGB_MOD, _______, - _______, _______, _______, _______, _______, _______, _______, _______, _______, RGB_TOG, _______, _______, RGB_MOD, RGB_HUI, _______, - _______, _______, _______, _______, KC_CALC, _______, _______, _______, KC_MUTE, KC_VOLD, KC_VOLU, _______, _______, RGB_VAI, _______, - _______, GU_TOGG, _______, _______, _______, _______, _______, RGB_SAD, RGB_VAD, RGB_SAI), - - [MAC_B] = LAYOUT_all( /* Base */ - KC_ESC, KC_BRID, KC_BRIU, KC_MCTL, KC_LPAD, KC_F5, KC_F6, KC_MPRV, KC_MPLY, KC_MNXT, KC_MUTE, KC_VOLD,KC_VOLU, KC_DEL, KC_MUTE, - KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS,KC_EQL, KC_BSPC, KC_HOME, - KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC,KC_RBRC, KC_BSLS, KC_PGUP, - KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT,KC_NUHS, KC_ENT, KC_PGDN, - KC_LSFT, KC_NUBS, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP, KC_END, - KC_LCTL, KC_LALT, KC_LGUI, KC_SPC, KC_RGUI, MO(MAC_FN),KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT), - - [MAC_W] = LAYOUT_all( /* WASD/�������� */ - _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, _______, KC_UP, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, KC_LEFT, KC_DOWN, KC_RGHT, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, - _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_W, _______, - _______, _______, _______, _______, _______, MO(MAC_FN), _______, KC_A, KC_S, KC_D), - - [MAC_FN] = LAYOUT_all( /* FN */ - _______, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_DEL, RGB_MOD, - _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, RGB_SPD, RGB_SPI, _______, _______, - _______, _______,TG(MAC_W),_______, _______, _______, _______, _______, KC_INS, DF(WIN_B),KC_PSCR,_______, _______, RGB_MOD, _______, - _______, _______, _______, _______, _______, _______, _______, _______, _______, RGB_TOG, _______, _______, RGB_MOD, RGB_HUI, _______, - _______, _______, _______, _______, KC_CALC, _______, _______, _______, KC_MUTE, KC_VOLD, KC_VOLU, _______, _______, RGB_VAI, _______, - _______, _______, _______, _______, _______, _______, _______, RGB_SAD, RGB_VAD, RGB_SAI) -}; - - - - -// clang-format off -#if defined(ENCODER_MAP_ENABLE) -const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][2] = { - [WIN_B] = { ENCODER_CCW_CW(KC_VOLU, KC_VOLD) }, - [WIN_W] = { ENCODER_CCW_CW(KC_VOLU, KC_VOLD) }, - [WIN_FN] = { ENCODER_CCW_CW(KC_VOLU, KC_VOLD) }, - [MAC_B] = { ENCODER_CCW_CW(KC_VOLU, KC_VOLD) }, - [MAC_W] = { ENCODER_CCW_CW(KC_VOLU, KC_VOLD) }, - [MAC_FN] = { ENCODER_CCW_CW(KC_VOLU, KC_VOLD) } -}; -#endif diff --git a/keyboards/neo/neo65/keymaps/via/rules.mk b/keyboards/neo/neo65/keymaps/via/rules.mk deleted file mode 100644 index 4253f57..0000000 --- a/keyboards/neo/neo65/keymaps/via/rules.mk +++ /dev/null @@ -1,2 +0,0 @@ -VIA_ENABLE = yes -ENCODER_MAP_ENABLE = yes \ No newline at end of file diff --git a/keyboards/neo/neo65/mcuconf.h b/keyboards/neo/neo65/mcuconf.h deleted file mode 100644 index 0d16f4f..0000000 --- a/keyboards/neo/neo65/mcuconf.h +++ /dev/null @@ -1,24 +0,0 @@ -/* Copyright (C) 2022 jonylee@hfd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include_next - -#undef WB32_SPI_USE_QSPI -#define WB32_SPI_USE_QSPI TRUE - -#undef WB32_I2C_USE_I2C1 -#define WB32_I2C_USE_I2C1 TRUE diff --git a/keyboards/neo/neo65/neo65.c b/keyboards/neo/neo65/neo65.c deleted file mode 100644 index 61b9015..0000000 --- a/keyboards/neo/neo65/neo65.c +++ /dev/null @@ -1,17 +0,0 @@ -/* Copyright (C) 2022 jonylee@hfd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "quantum.h" diff --git a/keyboards/neo/neo65/rules.mk b/keyboards/neo/neo65/rules.mk deleted file mode 100644 index 0dc7a33..0000000 --- a/keyboards/neo/neo65/rules.mk +++ /dev/null @@ -1,3 +0,0 @@ -EEPROM_DRIVER = wear_leveling -WEAR_LEVELING_DRIVER = spi_flash - diff --git a/patches/0002-add-edthu-wireless-support.patch b/patches/0002-add-edthu-wireless-support.patch new file mode 100644 index 0000000..95ff2a6 --- /dev/null +++ b/patches/0002-add-edthu-wireless-support.patch @@ -0,0 +1,2663 @@ +diff --git a/keyboards/neo/neo65_trimode/config.h b/keyboards/neo/neo65_trimode/config.h +new file mode 100644 +index 0000000..07acb06 +--- /dev/null ++++ b/keyboards/neo/neo65_trimode/config.h +@@ -0,0 +1,50 @@ ++// Copyright 2025 emolitor (github.com/emolitor) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#pragma once ++ ++/* ++ * Specific tapping term for wireless configuration. If the key is held for ++ * less than 3 seconds we select that device for the active connection. If ++ * greater than 3 seconds we select that device and go into pairing mode. ++ */ ++#define WIRELESS_TAPPING_TERM 3000 ++ ++/* LEDS */ ++#define LED_BLINK_FAST_PERIOD_MS 300 ++ ++ ++/* KEYS */ ++#define ESCAPE_PIN D2 ++#define DEVS_BT1_PIN C11 ++#define DEVS_BT2_PIN C10 ++#define DEVS_BT3_PIN A15 ++#define DEVS_2G4_PIN C0 ++ ++/* FLASH */ ++#define SPI_DRIVER SPIDQ ++#define SPI_SCK_PIN B3 ++#define SPI_MOSI_PIN B5 ++#define SPI_MISO_PIN B4 ++#define SPI_MOSI_PAL_MODE 5 ++#define EXTERNAL_FLASH_SPI_SLAVE_SELECT_PIN C12 ++ ++/* POWER */ ++#define USB_POWER_EN_PIN A14 ++//#define LED_POWER_EN_PIN B13 ++#define BT_CABLE_PIN B8 // High when charging ++#define BT_CHARGE_PIN B9 // Low when charging, high when fully charged ++ ++/* UART */ ++#define UART_TX_PIN A9 ++#define UART_TX_PAL_MODE 7 ++#define UART_RX_PIN A10 ++#define UART_RX_PAL_MODE 7 ++ ++#define LED_PIN_ON_STATE 0 ++ ++/* WIRELESS NAMES */ ++#define MD_BT_NAME "NEO65 BT$" ++ ++#define LPWR_TIMEOUT 120000 ++#define WLS_KEYBOARD_REPORT_KEYS 5 +diff --git a/keyboards/neo/neo65_trimode/halconf.h b/keyboards/neo/neo65_trimode/halconf.h +new file mode 100644 +index 0000000..38c22d2 +--- /dev/null ++++ b/keyboards/neo/neo65_trimode/halconf.h +@@ -0,0 +1,5 @@ ++#pragma once ++ ++#define PAL_USE_CALLBACKS TRUE ++ ++#include_next +diff --git a/keyboards/neo/neo65_trimode/keyboard.json b/keyboards/neo/neo65_trimode/keyboard.json +new file mode 100644 +index 0000000..cf7324b +--- /dev/null ++++ b/keyboards/neo/neo65_trimode/keyboard.json +@@ -0,0 +1,137 @@ ++{ ++ "manufacturer": "NEO", ++ "keyboard_name": "NEO65", ++ "maintainer": "emolitor@github", ++ "url": "https://www.qwertykeys.com/products/neo65-1", ++ "processor": "WB32FQ95", ++ "bootloader": "wb32-dfu", ++ "usb": { ++ "device_version": "0.0.1", ++ "vid": "0xFFFE", ++ "pid": "0x0015", ++ "suspend_wakeup_delay": 1000, ++ "no_startup_check": true ++ }, ++ "diode_direction": "ROW2COL", ++ "eeprom": { ++ "driver": "wear_leveling", ++ "wear_leveling": { ++ "backing_size": 8192, ++ "driver": "spi_flash" ++ } ++ }, ++ "host": { ++ "default": { ++ "nkro": true ++ } ++ }, ++ "features": { ++ "bootmagic": true, ++ "command": false, ++ "console": false, ++ "deferred_exec": true, ++ "extrakey": true, ++ "mousekey": true, ++ "nkro": true, ++ "macros": true ++ }, ++ "tapping": { ++ "term_per_key": true ++ }, ++ "keycodes": [ ++ { "key": "KC_USB" }, ++ { "key": "KC_BT1" }, ++ { "key": "KC_BT2" }, ++ { "key": "KC_BT3" }, ++ { "key": "KC_2G4" }, ++ { "key": "KC_NXT" } ++ ], ++ "matrix_pins": { ++ "cols": ["C1", "C2", "C3", "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "C4", "C5", "B0", "B1", "B2"], ++ "rows": ["C6", "C7", "C8", "C9", "B14"] ++ }, ++ "indicators": { ++ "caps_lock": "A8", ++ "on_state": 0 ++ }, ++ "layouts": { ++ "LAYOUT": { ++ "layout": [ ++ { "matrix": [0,0], "x": 0, "y": 0 }, ++ { "matrix": [0,1], "x": 1, "y": 0 }, ++ { "matrix": [0,2], "x": 2, "y": 0 }, ++ { "matrix": [0,3], "x": 3, "y": 0 }, ++ { "matrix": [0,4], "x": 4, "y": 0 }, ++ { "matrix": [0,5], "x": 5, "y": 0 }, ++ { "matrix": [0,6], "x": 6, "y": 0 }, ++ { "matrix": [0,7], "x": 7, "y": 0 }, ++ { "matrix": [0,8], "x": 8, "y": 0 }, ++ { "matrix": [0,9], "x": 9, "y": 0 }, ++ { "matrix": [0,10], "x": 10, "y": 0 }, ++ { "matrix": [0,11], "x": 11, "y": 0 }, ++ { "matrix": [0,12], "x": 12, "y": 0 }, ++ { "matrix": [3,12], "x": 13, "y": 0 }, ++ { "matrix": [0,13], "w": 2, "x": 13, "y": 0 }, ++ { "matrix": [0,15], "x": 15, "y": 0 }, ++ ++ { "matrix": [1,0], "w": 1.5, "x": 0, "y": 1 }, ++ { "matrix": [1,1], "x": 1.5, "y": 1 }, ++ { "matrix": [1,2], "x": 2.5, "y": 1 }, ++ { "matrix": [1,3], "x": 3.5, "y": 1 }, ++ { "matrix": [1,4], "x": 4.5, "y": 1 }, ++ { "matrix": [1,5], "x": 5.5, "y": 1 }, ++ { "matrix": [1,6], "x": 6.5, "y": 1 }, ++ { "matrix": [1,7], "x": 7.5, "y": 1 }, ++ { "matrix": [1,8], "x": 8.5, "y": 1 }, ++ { "matrix": [1,9], "x": 9.5, "y": 1 }, ++ { "matrix": [1,10], "x": 10.5, "y": 1 }, ++ { "matrix": [1,11], "x": 11.5, "y": 1 }, ++ { "matrix": [1,12], "x": 12.5, "y": 1 }, ++ { "matrix": [1,13], "w": 1.5, "x": 13.5, "y": 1 }, ++ { "matrix": [1,15], "x": 15, "y": 1 }, ++ ++ { "matrix": [2,0], "w": 1.75, "x": 0, "y": 2 }, ++ { "matrix": [2,1], "x": 1.75, "y": 2 }, ++ { "matrix": [2,2], "x": 2.75, "y": 2 }, ++ { "matrix": [2,3], "x": 3.75, "y": 2 }, ++ { "matrix": [2,4], "x": 4.75, "y": 2 }, ++ { "matrix": [2,5], "x": 5.75, "y": 2 }, ++ { "matrix": [2,6], "x": 6.75, "y": 2 }, ++ { "matrix": [2,7], "x": 7.75, "y": 2 }, ++ { "matrix": [2,8], "x": 8.75, "y": 2 }, ++ { "matrix": [2,9], "x": 9.75, "y": 2 }, ++ { "matrix": [2,10], "x": 10.75, "y": 2 }, ++ { "matrix": [2,11], "x": 11.75, "y": 2 }, ++ { "matrix": [2,12], "x": 12.75, "y": 2 }, ++ { "matrix": [2,13], "w": 2.25, "x": 12.75, "y": 2 }, ++ { "matrix": [2,15], "x": 15, "y": 2 }, ++ ++ { "matrix": [3,0], "w": 2.25, "x": 0, "y": 3 }, ++ { "matrix": [3,1], "x": 1.25, "y": 3 }, ++ { "matrix": [3,2], "x": 2.25, "y": 3 }, ++ { "matrix": [3,3], "x": 3.25, "y": 3 }, ++ { "matrix": [3,4], "x": 4.25, "y": 3 }, ++ { "matrix": [3,5], "x": 5.25, "y": 3 }, ++ { "matrix": [3,6], "x": 6.25, "y": 3 }, ++ { "matrix": [3,7], "x": 7.25, "y": 3 }, ++ { "matrix": [3,8], "x": 8.25, "y": 3 }, ++ { "matrix": [3,9], "x": 9.25, "y": 3 }, ++ { "matrix": [3,10], "x": 10.25, "y": 3 }, ++ { "matrix": [3,11], "x": 11.25, "y": 3 }, ++ { "matrix": [3,13], "w": 1.75, "x": 12.25, "y": 3 }, ++ { "matrix": [3,14], "x": 14, "y": 3 }, ++ { "matrix": [3,15], "x": 15, "y": 3 }, ++ ++ { "matrix": [4,0], "w": 1.25, "x": 0, "y": 4 }, ++ { "matrix": [4,1], "w": 1.25, "x": 1.25, "y": 4 }, ++ { "matrix": [4,2], "w": 1.25, "x": 2.5, "y": 4 }, ++ { "matrix": [4,6], "w": 6.25, "x": 3.75, "y": 4 }, ++ { "matrix": [4,9], "w": 1.25, "x": 10, "y": 4 }, ++ { "matrix": [4,10], "w": 1.25, "x": 11.25, "y": 4 }, ++ { "matrix": [4,13], "x": 13, "y": 4 }, ++ { "matrix": [4,14], "x": 14, "y": 4 }, ++ { "matrix": [4,15], "x": 15, "y": 4 } ++ ] ++ } ++ } ++} +diff --git a/keyboards/neo/neo65_trimode/keymaps/default/keymap.c b/keyboards/neo/neo65_trimode/keymaps/default/keymap.c +new file mode 100644 +index 0000000..f7f37e9 +--- /dev/null ++++ b/keyboards/neo/neo65_trimode/keymaps/default/keymap.c +@@ -0,0 +1,47 @@ ++// Copyright 2025 emolitor (@github.com/emolitor) ++// Copyright 2023 JoyLee (@itarze) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include QMK_KEYBOARD_H ++ ++enum layers { ++ WIN_BASE, ++ WIN_FN, ++ MAC_BASE, ++ MAC_FN, ++}; ++ ++// clang-format off ++const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { ++ [WIN_BASE] = LAYOUT( ++ KC_ESC, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSLS, KC_BSPC, KC_DEL, ++ KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_PGUP, ++ KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_NUHS, KC_ENT, KC_PGDN, ++ KC_LSFT, KC_NUBS, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP, KC_END, ++ KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, KC_RALT, MO(WIN_FN), KC_LEFT, KC_DOWN, KC_RGHT ++ ), ++ ++ [WIN_FN] = LAYOUT( ++ KC_USB, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, _______, _______, EE_CLR, ++ KC_NXT, LT(0, KC_BT1), LT(0, KC_BT2), LT(0, KC_BT3), LT(0, KC_2G4), _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, ++ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, ++ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, ++ _______, _______, _______, _______, _______, _______, _______, _______, _______ ++ ), ++ ++ [MAC_BASE] = LAYOUT( ++ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, ++ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, ++ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, ++ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, ++ _______, _______, _______, _______, _______, _______, _______, _______, _______ ++ ), ++ ++ [MAC_FN] = LAYOUT( ++ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, ++ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, ++ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, ++ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, ++ _______, _______, _______, _______, _______, _______, _______, _______, _______ ++ ) ++}; +diff --git a/keyboards/neo/neo65_trimode/mcuconf.h b/keyboards/neo/neo65_trimode/mcuconf.h +new file mode 100644 +index 0000000..63fe01f +--- /dev/null ++++ b/keyboards/neo/neo65_trimode/mcuconf.h +@@ -0,0 +1,13 @@ ++// Copyright 2025 emolitor github.com/emolitor) ++// Copyright (C) 2022 jonylee@hfd ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#pragma once ++ ++#include_next ++ ++#undef WB32_SERIAL_USE_UART1 ++#define WB32_SERIAL_USE_UART1 TRUE ++ ++#undef WB32_SPI_USE_QSPI ++#define WB32_SPI_USE_QSPI TRUE +diff --git a/keyboards/neo/neo65_trimode/neo65_trimode.c b/keyboards/neo/neo65_trimode/neo65_trimode.c +new file mode 100644 +index 0000000..25a4bd1 +--- /dev/null ++++ b/keyboards/neo/neo65_trimode/neo65_trimode.c +@@ -0,0 +1,348 @@ ++// Copyright 2025 emolitor (github.com/emolitor) ++// Copyright 2024 Westberry Technology (ChangZhou) Corp., Ltd ++// SPDX-License-Identifier: GPL-2.0-or-later*/ ++ ++#include QMK_KEYBOARD_H ++#include "wireless.h" ++ ++typedef union { ++ uint32_t raw; ++ struct { ++ uint8_t flag : 1; ++ uint8_t devs : 3; ++ }; ++} confinfo_t; ++confinfo_t confinfo; ++ ++uint32_t post_init_timer = 0x00; ++ ++// Possible LED states. ++enum { LED_OFF = 0, LED_ON = 1, LED_BLINK_SLOW = 2, LED_BLINK_FAST = 3 }; ++ ++// The size of this should match the number of devices in the device index in ++// westberry/wireless/module.h. Default to zero to set all LEDs to a default ++// state of OFF. ++static uint8_t led_blink_state[7] = {0}; ++ ++// Implement a circular linked list of devices to support FN+TAB device ++// selection ++struct devs_list { ++ int devs; ++ struct devs_list *next; ++}; ++ ++struct devs_list devs[] = { ++ {.devs = DEVS_USB, .next = &devs[1]}, ++ {.devs = DEVS_BT1, .next = &devs[2]}, ++ {.devs = DEVS_BT2, .next = &devs[3]}, ++ {.devs = DEVS_BT3, .next = &devs[4]}, ++ {.devs = DEVS_2G4, .next = &devs[0]} ++}; ++ ++struct devs_list *current_dev = &devs[0]; // Default circular linked list to USB device ++ ++// Hack ++void md_send_devinfo(const char *name); ++ ++// We use per-key tapping term to allow the wireless keys to have a much ++// longer tapping term, therefore a longer hold, to match the default ++// firmware behaviour. ++uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { ++ switch (keycode) { ++ case LT(0, KC_BT1): ++ case LT(0, KC_BT2): ++ case LT(0, KC_BT3): ++ case LT(0, KC_2G4): ++ return WIRELESS_TAPPING_TERM; ++ default: ++ return TAPPING_TERM; ++ } ++} ++ ++uint32_t led_blink_callback(uint32_t trigger_time, void *cb_arg) { ++ static const uint8_t pattern[4] = {0x00, 0xff, 0x0f, 0xaa}; ++ static uint8_t phase = 0; ++ phase = (phase + 1) % 8; ++ ++ // Default all connection indicators to off ++ led_blink_state[DEVS_USB] = LED_OFF; ++ led_blink_state[DEVS_BT1] = LED_OFF; ++ led_blink_state[DEVS_BT2] = LED_OFF; ++ led_blink_state[DEVS_BT3] = LED_OFF; ++ led_blink_state[DEVS_2G4] = LED_OFF; ++ ++ // Set active indicator LED mode ++ if (*md_getp_state() == MD_STATE_PAIRING) { ++ led_blink_state[confinfo.devs] = LED_BLINK_FAST; ++ } else if (confinfo.devs != DEVS_USB && *md_getp_state() != MD_STATE_CONNECTED) { ++ led_blink_state[confinfo.devs] = LED_BLINK_SLOW; ++ } else { ++ led_blink_state[confinfo.devs] = LED_ON; ++ } ++ ++ uint8_t bit = 1 << phase; ++ gpio_write_pin(ESCAPE_PIN, (pattern[led_blink_state[DEVS_USB]] & bit) != 0); ++ gpio_write_pin(DEVS_BT1_PIN, (pattern[led_blink_state[DEVS_BT1]] & bit) != 0); ++ gpio_write_pin(DEVS_BT2_PIN, (pattern[led_blink_state[DEVS_BT2]] & bit) != 0); ++ gpio_write_pin(DEVS_BT3_PIN, (pattern[led_blink_state[DEVS_BT3]] & bit) != 0); ++ gpio_write_pin(DEVS_2G4_PIN, (pattern[led_blink_state[DEVS_2G4]] & bit) != 0); ++ ++ return LED_BLINK_FAST_PERIOD_MS / 2; ++} ++ ++void keyboard_post_init_kb(void) { ++ confinfo.raw = eeconfig_read_kb(); ++ if (!confinfo.raw) { ++ confinfo.flag = true; ++ confinfo.devs = DEVS_USB; ++ eeconfig_update_kb(confinfo.raw); ++ } ++ ++ gpio_set_pin_output(ESCAPE_PIN); ++ gpio_set_pin_output(DEVS_BT1_PIN); ++ gpio_set_pin_output(DEVS_BT2_PIN); ++ gpio_set_pin_output(DEVS_BT3_PIN); ++ gpio_set_pin_output(DEVS_2G4_PIN); ++ ++ // Set GPIO as high input for battery charging state ++ //gpio_set_pin_input(BT_CABLE_PIN); ++ //gpio_set_pin_input_high(BT_CHARGE_PIN); ++ ++ // Set USB_POWER_EN_PIN state before enabling the output to avoid instability ++ if (confinfo.devs == DEVS_USB && gpio_read_pin(BT_CABLE_PIN)) { ++ gpio_write_pin_low(USB_POWER_EN_PIN); ++ } else { ++ gpio_write_pin_high(USB_POWER_EN_PIN); ++ } ++ gpio_set_pin_output(USB_POWER_EN_PIN); ++ ++ wireless_init(); ++ md_send_devinfo(MD_BT_NAME); ++ wait_ms(10); ++ wireless_devs_change(!confinfo.devs, confinfo.devs, false); ++ post_init_timer = timer_read32(); ++ ++ defer_exec(LED_BLINK_FAST_PERIOD_MS / 2, led_blink_callback, NULL); ++ ++ keyboard_post_init_user(); ++} ++ ++void usb_power_connect(void) { ++ gpio_write_pin_low(USB_POWER_EN_PIN); ++} ++ ++void usb_power_disconnect(void) { ++ gpio_write_pin_high(USB_POWER_EN_PIN); ++} ++ ++void suspend_wakeup_init_kb(void) { ++ wireless_devs_change(wireless_get_current_devs(), wireless_get_current_devs(), false); ++ suspend_wakeup_init_user(); ++} ++ ++bool lpwr_is_allow_timeout_hook(void) { ++ if (wireless_get_current_devs() == DEVS_USB) { ++ return false; ++ } ++ ++ return true; ++} ++ ++void wireless_post_task(void) { ++ if (post_init_timer && timer_elapsed32(post_init_timer) >= 100) { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_FW_VERSION); // get the module fw version. ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_SLEEP_BT_EN); // timeout 30min to sleep in bt mode, enable ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_SLEEP_2G4_EN); // timeout 30min to sleep in 2.4g mode, enable ++ wireless_devs_change(!confinfo.devs, confinfo.devs, false); ++ post_init_timer = 0x00; ++ } ++} ++ ++void md_devs_change(uint8_t devs, bool reset) { ++ switch (devs) { ++ case DEVS_USB: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_USB); ++ } break; ++ case DEVS_2G4: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_2G4); ++ if (reset) { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); ++ } ++ } break; ++ case DEVS_BT1: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_BT1); ++ if (reset) { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); ++ } ++ } break; ++ case DEVS_BT2: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_BT2); ++ if (reset) { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); ++ } ++ } break; ++ case DEVS_BT3: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_BT3); ++ if (reset) { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); ++ } ++ } break; ++ default: ++ break; ++ } ++} ++ ++bool process_record_kb(uint16_t keycode, keyrecord_t *record) { ++ if (process_record_user(keycode, record) != true) { ++ return false; ++ } ++ ++ switch (keycode) { ++ case KC_USB: { ++ wireless_devs_change(wireless_get_current_devs(), DEVS_USB, false); ++ return false; ++ } ++ case KC_NXT: { ++ if (record->event.pressed) { ++ current_dev = current_dev->next; ++ wireless_devs_change(wireless_get_current_devs(), current_dev->devs, false); ++ return false; ++ } ++ } ++ case LT(0, KC_BT1): { ++ if (record->tap.count && record->event.pressed) { ++ wireless_devs_change(wireless_get_current_devs(), DEVS_BT1, false); ++ } else if (record->event.pressed && *md_getp_state() != MD_STATE_PAIRING) { ++ wireless_devs_change(wireless_get_current_devs(), DEVS_BT1, true); ++ } ++ return false; ++ } ++ case LT(0, KC_BT2): { ++ if (record->tap.count && record->event.pressed) { ++ wireless_devs_change(wireless_get_current_devs(), DEVS_BT2, false); ++ } else if (record->event.pressed && *md_getp_state() != MD_STATE_PAIRING) { ++ wireless_devs_change(wireless_get_current_devs(), DEVS_BT2, true); ++ } ++ return false; ++ } ++ case LT(0, KC_BT3): { ++ if (record->tap.count && record->event.pressed) { ++ wireless_devs_change(wireless_get_current_devs(), DEVS_BT3, false); ++ } else if (record->event.pressed && *md_getp_state() != MD_STATE_PAIRING) { ++ wireless_devs_change(wireless_get_current_devs(), DEVS_BT3, true); ++ } ++ return false; ++ } ++ case LT(0, KC_2G4): { ++ if (record->tap.count && record->event.pressed) { ++ wireless_devs_change(wireless_get_current_devs(), DEVS_2G4, false); ++ } else if (record->event.pressed && *md_getp_state() != MD_STATE_PAIRING) { ++ wireless_devs_change(wireless_get_current_devs(), DEVS_2G4, true); ++ } ++ return false; ++ } ++ default: ++ return true; ++ } ++} ++ ++void wireless_devs_change_kb(uint8_t old_devs, uint8_t new_devs, bool reset) { ++ if (confinfo.devs != wireless_get_current_devs()) { ++ confinfo.devs = wireless_get_current_devs(); ++ eeconfig_update_kb(confinfo.raw); ++ } ++} ++ ++// Force MCU reset on unhandled_exception ++void _unhandled_exception(void) { ++ mcu_reset(); ++} ++ ++// Exprimental change to fix duplicate and hung key presses on wireless ++void wireless_send_nkro(report_nkro_t *report) { ++ static report_keyboard_t temp_report_keyboard = {0}; ++ uint8_t wls_report_nkro[MD_SND_CMD_NKRO_LEN] = {0}; ++ ++#ifdef NKRO_ENABLE ++ if (report != NULL) { ++ report_nkro_t temp_report_nkro = *report; ++ uint8_t key_count = 0; ++ ++ temp_report_keyboard.mods = temp_report_nkro.mods; ++ for (uint8_t i = 0; i < NKRO_REPORT_BITS; i++) { ++ key_count += __builtin_popcount(temp_report_nkro.bits[i]); ++ } ++ ++ /* ++ * Use NKRO for sending when more than 6 keys are pressed ++ * to solve the issue of the lack of a protocol flag in wireless mode. ++ */ ++ ++ for (uint8_t i = 0; i < key_count; i++) { ++ uint8_t usageid; ++ uint8_t idx, n = 0; ++ ++ for (n = 0; n < NKRO_REPORT_BITS && !temp_report_nkro.bits[n]; n++) { ++ } ++ usageid = (n << 3) | biton(temp_report_nkro.bits[n]); ++ del_key_bit(&temp_report_nkro, usageid); ++ ++ for (idx = 0; idx < WLS_KEYBOARD_REPORT_KEYS; idx++) { ++ if (temp_report_keyboard.keys[idx] == usageid) { ++ goto next; ++ } ++ } ++ ++ for (idx = 0; idx < WLS_KEYBOARD_REPORT_KEYS; idx++) { ++ if (temp_report_keyboard.keys[idx] == 0x00) { ++ temp_report_keyboard.keys[idx] = usageid; ++ break; ++ } ++ } ++ next: ++ if (idx == WLS_KEYBOARD_REPORT_KEYS && (usageid < (MD_SND_CMD_NKRO_LEN * 8))) { ++ wls_report_nkro[usageid / 8] |= 0x01 << (usageid % 8); ++ } ++ } ++ ++ temp_report_nkro = *report; ++ ++ // find key up and del it. ++ uint8_t nkro_keys = key_count; ++ for (uint8_t i = 0; i < WLS_KEYBOARD_REPORT_KEYS; i++) { ++ report_nkro_t found_report_nkro; ++ uint8_t usageid = 0x00; ++ uint8_t n; ++ ++ found_report_nkro = temp_report_nkro; ++ ++ for (uint8_t c = 0; c < nkro_keys; c++) { ++ for (n = 0; n < NKRO_REPORT_BITS && !found_report_nkro.bits[n]; n++) { ++ } ++ usageid = (n << 3) | biton(found_report_nkro.bits[n]); ++ del_key_bit(&found_report_nkro, usageid); ++ if (usageid == temp_report_keyboard.keys[i]) { ++ del_key_bit(&temp_report_nkro, usageid); ++ nkro_keys--; ++ break; ++ } ++ } ++ ++ if (usageid != temp_report_keyboard.keys[i]) { ++ temp_report_keyboard.keys[i] = 0x00; ++ } ++ } ++ ++ } else { ++ memset(&temp_report_keyboard, 0, sizeof(temp_report_keyboard)); ++ } ++#endif ++ void wireless_task(void); ++ bool smsg_is_busy(void); ++ while (smsg_is_busy()) { ++ wireless_task(); ++ } ++ extern host_driver_t wireless_driver; ++ wireless_driver.send_keyboard(&temp_report_keyboard); ++ md_send_nkro(wls_report_nkro); ++} +diff --git a/keyboards/neo/neo65_trimode/readme.md b/keyboards/neo/neo65_trimode/readme.md +new file mode 100644 +index 0000000..8100893 +--- /dev/null ++++ b/keyboards/neo/neo65_trimode/readme.md +@@ -0,0 +1,43 @@ ++# Neo65 Tri-mode ++The Neo65 Tri-mode is based on a Westberry Tech WB32FQ95 MCU in a LQFP64 ++package and can be programmed with wb32-dfu. The keyboard uses a WCH CH582F ++chip running a proprietary firmware to provide wireless connectivity. This is ++a community port which was derived from a partial source originally released ++by the manufacturer and reverse engineered by probing the hardware directly. ++ ++ ++## Flashing a new firmware ++Hold ESCAPE [0,0] to enter bootloader mode while inserting the USB cable into ++the keyboard. Then run the following to flash the firmware. ++```shell ++qmk flash -kb neo/neo65_trimode -km default ++``` ++ ++ ++## Quirks ++The Neo65 uses a slightly different way to initialize the bluetooth device ++names. As the wireless stack is initialized in `keyboard_post_init_kb` a call ++is made to `md_send_devinfo` where the bluetooth name is passed but with the ++`$` character as the last character. The `$` is replaced with the BT device ++number when pairing. This seems to be a feature unique to this version of the ++CH582F firmware. This also means during pairing we do not 'CLEAN' the device ++and we do not upddate the device name. We simply send PAIR to `md_send_devctrl`. ++ ++ ++## How are the LEDs wired? ++* PD2 is wired to the ESC LED ++* PA8 is wired to the CAPSLOCK LED ++* PC11 is wired to the Q LED ++* PC10 is wired to the W LED ++* PA15 is wired to the E LED ++* PC0 is wired to the R LED ++ ++ ++## Reference Material ++The following shouldn't be upstreamed but I've included documentation relevant ++for porting in the repo to have everything in a single place. ++* [WB32FQ95 Data Sheet](../../../em-documentation/EN_DS1104041_WB32FQ95xC_V01.pdf) ++* [WB32FQ95 Reference Manual](../../../em-documentation/EN_RM2905025_WB32FQ95xx_V01.pdf) ++* [WCH CH582F Data Sheet](../../../em-documentation/CH583DS1.PDF) ++* [Puya P25D80SH Data Sheet](../../../em-documentation/PUYA-P25Q80H-SSH-IT_C194872.pdf) ++* [TP4056 Data Sheet](../../../em-documentation/TP4056.pdf) +diff --git a/keyboards/neo/neo65_trimode/rules.mk b/keyboards/neo/neo65_trimode/rules.mk +new file mode 100644 +index 0000000..a38990f +--- /dev/null ++++ b/keyboards/neo/neo65_trimode/rules.mk +@@ -0,0 +1 @@ ++include keyboards/neo/wireless/wireless.mk +diff --git a/keyboards/neo/wireless/lowpower.c b/keyboards/neo/wireless/lowpower.c +new file mode 100644 +index 0000000..ab530f9 +--- /dev/null ++++ b/keyboards/neo/wireless/lowpower.c +@@ -0,0 +1,312 @@ ++// Copyright 2024 Su (@isuua) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include "quantum.h" ++#include "wireless.h" ++#include "usb_main.h" ++ ++#ifndef LPWR_TIMEOUT ++# define LPWR_TIMEOUT 300000 // 5min ++#endif ++ ++#ifndef LPWR_PRESLEEP_DELAY ++# define LPWR_PRESLEEP_DELAY 200 ++#endif ++ ++#ifndef LPWR_STOP_DELAY ++# define LPWR_STOP_DELAY 200 ++#endif ++ ++#ifndef LPWR_WAKEUP_DELAY ++# define LPWR_WAKEUP_DELAY 200 ++#endif ++ ++static lpwr_state_t lpwr_state = LPWR_NORMAL; ++static lpwr_mode_t lpwr_mode = LPWR_MODE_TIMEOUT; ++static uint32_t lpwr_timeout_value = LPWR_TIMEOUT; ++static uint32_t lpwr_timestamp = 0x00; ++static lpwr_wakeupcd_t lpwr_wakeupcd = LPWR_WAKEUP_NONE; ++static bool manual_timeout = false; ++ ++static bool rgb_enable_bak = false; ++ ++void last_matrix_activity_trigger(void); ++ ++void lpwr_clock_enable(void); ++void lpwr_enter_stop(void); ++void lpwr_exti_init(void); ++void mcu_stop_mode(void); ++ ++extern void matrix_init_pins(void); ++ ++lpwr_state_t lpwr_get_state(void) { ++ return lpwr_state; ++} ++ ++void lpwr_set_state(lpwr_state_t state) { ++ lpwr_state = state; ++} ++ ++lpwr_mode_t lpwr_get_mode(void) { ++ return lpwr_mode; ++} ++ ++void lpwr_set_mode(lpwr_mode_t mode) { ++ lpwr_mode = mode; ++} ++ ++void lpwr_set_timeout_value(uint32_t timeout) { ++ lpwr_timeout_value = timeout; ++} ++ ++uint32_t lpwr_timeout_value_read(void) { ++ return lpwr_timeout_value; ++} ++ ++void lpwr_update_timestamp(void) { ++ lpwr_timestamp = sync_timer_read32(); ++} ++ ++uint32_t lpwr_timestamp_read(void) { ++ return lpwr_timestamp; ++} ++ ++void lpwr_set_sleep_wakeupcd(lpwr_wakeupcd_t wakeupcd) { ++ lpwr_wakeupcd = wakeupcd; ++} ++ ++lpwr_wakeupcd_t lpwr_get_sleep_wakeupcd(void) { ++ return lpwr_wakeupcd; ++} ++ ++void lpwr_clock_enable(void) __attribute__((weak)); ++void lpwr_clock_enable(void) {} ++ ++void lpwr_exti_init(void) __attribute__((weak)); ++void lpwr_exti_init(void) {} ++ ++void mcu_stop_mode(void) __attribute__((weak)); ++void mcu_stop_mode(void) {} ++ ++void lpwr_enter_stop(void) { ++ chSysLock(); ++ lpwr_exti_init(); ++ chSysUnlock(); ++ ++ chSysDisable(); ++ mcu_stop_mode(); ++ lpwr_clock_enable(); ++ matrix_init_pins(); ++ chSysEnable(); ++} ++ ++void lpwr_set_timeout_manual(bool enable) { ++ manual_timeout = enable; ++} ++ ++bool lpwr_get_timeout_manual(void) { ++ return manual_timeout; ++} ++ ++// 2.4g mode, host state ++void md_receive_host_cb(bool resume) { ++ ++ if (resume) { ++ if (lpwr_get_state() != LPWR_NORMAL) { ++ lpwr_update_timestamp(); ++ lpwr_set_state(LPWR_WAKEUP); ++ } ++ } else { ++ if (lpwr_get_state() == LPWR_NORMAL) { ++ manual_timeout = true; ++ } ++ } ++} ++ ++bool lpwr_is_allow_timeout_hook(void) __attribute__((weak)); ++bool lpwr_is_allow_timeout_hook(void) { ++ return true; ++} ++ ++bool lpwr_is_allow_timeout(void) __attribute__((weak)); ++bool lpwr_is_allow_timeout(void) { ++ uint32_t timeout = lpwr_timeout_value_read(); ++ ++ if (lpwr_is_allow_timeout_hook() != true) { ++ manual_timeout = false; ++ return false; ++ } ++ ++ //if ((wireless_get_current_devs() == DEVS_USB) && (USB_DRIVER.state == USB_ACTIVE)) { ++ if (wireless_get_current_devs() == DEVS_USB) { ++ manual_timeout = false; ++ return false; ++ } ++ ++ if (manual_timeout || (timeout && (last_input_activity_elapsed() >= timeout))) { ++ manual_timeout = false; ++ return true; ++ } ++ ++ return false; ++} ++ ++bool lpwr_is_allow_presleep_hook(void) __attribute__((weak)); ++bool lpwr_is_allow_presleep_hook(void) { ++ return true; ++} ++ ++bool lpwr_is_allow_presleep(void) __attribute__((weak)); ++bool lpwr_is_allow_presleep(void) { ++ uint32_t delay = LPWR_PRESLEEP_DELAY; ++ ++ if (lpwr_is_allow_presleep_hook() != true) { ++ return false; ++ } ++ ++ if (!delay || (sync_timer_elapsed32(lpwr_timestamp_read()) >= delay)) { ++ return true; ++ } ++ ++ return false; ++} ++ ++bool lpwr_is_allow_stop_hook(void) __attribute__((weak)); ++bool lpwr_is_allow_stop_hook(void) { ++ return true; ++} ++ ++bool lpwr_is_allow_stop(void) __attribute__((weak)); ++bool lpwr_is_allow_stop(void) { ++ uint32_t delay = LPWR_STOP_DELAY; ++ ++ if (lpwr_is_allow_stop_hook() != true) { ++ return false; ++ } ++ ++ if (!delay || (sync_timer_elapsed32(lpwr_timestamp_read()) >= delay)) { ++ return true; ++ } ++ ++ return false; ++} ++ ++bool lpwr_is_allow_wakeup_hook(void) __attribute__((weak)); ++bool lpwr_is_allow_wakeup_hook(void) { ++ return true; ++} ++ ++bool lpwr_is_allow_wakeup(void) __attribute__((weak)); ++bool lpwr_is_allow_wakeup(void) { ++ uint32_t delay = LPWR_WAKEUP_DELAY; ++ ++ if (lpwr_is_allow_wakeup_hook() != true) { ++ return false; ++ } ++ ++ if (!delay || (sync_timer_elapsed32(lpwr_timestamp_read()) >= delay)) { ++ return true; ++ } ++ ++ return false; ++} ++ ++void lpwr_presleep_hook(void) __attribute__((weak)); ++void lpwr_presleep_hook(void) {} ++ ++void lpwr_presleep_cb(void) __attribute__((weak)); ++void lpwr_presleep_cb(void) { ++ ++#if defined(RGB_MATRIX_ENABLE) ++ rgb_enable_bak = rgb_matrix_is_enabled(); ++ rgb_matrix_disable_noeeprom(); ++#elif defined(RGBLIGHT_ENABLE) ++ rgb_enable_bak = rgblight_is_enabled(); ++ rgblight_disable_noeeprom(); ++#else ++ rgb_enable_bak = false; ++#endif ++ suspend_power_down(); ++ lpwr_presleep_hook(); ++} ++ ++void lpwr_stop_hook_pre(void) __attribute__((weak)); ++void lpwr_stop_hook_pre(void) {} ++ ++void lpwr_stop_hook_post(void) __attribute__((weak)); ++void lpwr_stop_hook_post(void) {} ++ ++void lpwr_stop_cb(void) __attribute__((weak)); ++void lpwr_stop_cb(void) { ++ ++ lpwr_set_sleep_wakeupcd(LPWR_WAKEUP_NONE); ++ ++ lpwr_stop_hook_pre(); ++ lpwr_enter_stop(); ++ ++ switch (lpwr_get_sleep_wakeupcd()) { ++ case LPWR_WAKEUP_UART: { ++ lpwr_set_state(LPWR_STOP); ++ } break; ++ default: { ++ lpwr_set_state(LPWR_WAKEUP); ++ } break; ++ } ++ ++ lpwr_stop_hook_post(); ++} ++ ++void lpwr_wakeup_hook(void) __attribute__((weak)); ++void lpwr_wakeup_hook(void) {} ++ ++void lpwr_wakeup_cb(void) __attribute__((weak)); ++void lpwr_wakeup_cb(void) { ++ ++ if (rgb_enable_bak) { ++#if defined(RGB_MATRIX_ENABLE) ++ rgb_matrix_enable_noeeprom(); ++#elif defined(RGBLIGHT_ENABLE) ++ rgblight_enable_noeeprom(); ++#endif ++ } ++ ++ suspend_wakeup_init(); ++ lpwr_wakeup_hook(); ++ ++ last_matrix_activity_trigger(); ++} ++ ++void lpwr_task(void) __attribute__((weak)); ++void lpwr_task(void) { ++ ++ switch (lpwr_get_state()) { ++ case LPWR_NORMAL: { ++ if (lpwr_is_allow_timeout()) { ++ lpwr_update_timestamp(); ++ lpwr_set_state(LPWR_PRESLEEP); ++ } ++ } break; ++ case LPWR_PRESLEEP: { ++ if (lpwr_is_allow_presleep()) { ++ lpwr_presleep_cb(); ++ lpwr_update_timestamp(); ++ lpwr_set_state(LPWR_STOP); ++ } ++ } break; ++ case LPWR_STOP: { ++ if (lpwr_is_allow_stop()) { ++ lpwr_update_timestamp(); ++ lpwr_stop_cb(); ++ } ++ } break; ++ case LPWR_WAKEUP: { ++ if (lpwr_is_allow_wakeup()) { ++ lpwr_wakeup_cb(); ++ lpwr_update_timestamp(); ++ lpwr_set_state(LPWR_NORMAL); ++ } ++ } break; ++ default: ++ break; ++ } ++} +diff --git a/keyboards/neo/wireless/lowpower.h b/keyboards/neo/wireless/lowpower.h +new file mode 100644 +index 0000000..e7d60a4 +--- /dev/null ++++ b/keyboards/neo/wireless/lowpower.h +@@ -0,0 +1,39 @@ ++// Copyright 2024 Su (@isuua) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#pragma once ++ ++typedef enum { ++ LPWR_NORMAL = 0, ++ LPWR_PRESLEEP, ++ LPWR_STOP, ++ LPWR_WAKEUP, ++} lpwr_state_t; ++ ++typedef enum { ++ LPWR_WAKEUP_NONE = 0, ++ LPWR_WAKEUP_MATRIX, ++ LPWR_WAKEUP_UART, ++ LPWR_WAKEUP_CABLE, ++ LPWR_WAKEUP_USB, ++ LPWR_WAKEUP_ONEKEY, ++ LPWR_WAKEUP_ENCODER, ++ LPWR_WAKEUP_SWITCH, ++} lpwr_wakeupcd_t; ++ ++typedef enum { ++ LPWR_MODE_TIMEOUT = 0, ++} lpwr_mode_t; ++ ++lpwr_state_t lpwr_get_state(void); ++lpwr_mode_t lpwr_get_mode(void); ++uint32_t lpwr_timestamp_read(void); ++uint32_t lpwr_timeout_value_read(void); ++void lpwr_set_sleep_wakeupcd(lpwr_wakeupcd_t wakeupcd); ++lpwr_wakeupcd_t lpwr_get_sleep_wakeupcd(void); ++void lpwr_update_timestamp(void); ++void lpwr_set_timeout_manual(bool enable); ++bool lpwr_get_timeout_manual(void); ++void lpwr_set_state(lpwr_state_t state); ++void lpwr_set_mode(lpwr_mode_t mode); ++void lpwr_task(void); +diff --git a/keyboards/neo/wireless/lpwr_wb32.c b/keyboards/neo/wireless/lpwr_wb32.c +new file mode 100644 +index 0000000..65bc298 +--- /dev/null ++++ b/keyboards/neo/wireless/lpwr_wb32.c +@@ -0,0 +1,209 @@ ++// Copyright 2024 Su (@isuua) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include "quantum.h" ++#include "wireless.h" ++#include "util.h" ++ ++#ifndef LPWR_UART_WAKEUP_DISABLE ++# include "uart.h" ++#endif ++ ++static ioline_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS; ++static ioline_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS; ++ ++#if PAL_USE_CALLBACKS != TRUE ++# error PAL_USE_CALLBACKS must be set to TRUE! ++#endif ++ ++#if !((DIODE_DIRECTION == ROW2COL) || (DIODE_DIRECTION == COL2ROW)) ++# error DIODE_DIRECTION must be one of COL2ROW or ROW2COL! ++#endif ++ ++// clang-format off ++static const uint32_t pre_lp_code[] = {553863175u, 554459777u, 1208378049u, 4026624001u, 688390415u, 554227969u, 3204472833u, 1198571264u, 1073807360u, 1073808388u}; ++#define PRE_LP() ((void (*)(void))((unsigned int)(pre_lp_code) | 0x01))() ++ ++static const uint32_t post_lp_code[] = {553863177u, 554459777u, 1208509121u, 51443856u, 4026550535u, 1745485839u, 3489677954u, 536895496u, 673389632u, 1198578684u, 1073807360u, 536866816u, 1073808388u}; ++#define POST_LP() ((void (*)(void))((unsigned int)(post_lp_code) | 0x01))() ++// clang-format on ++ ++extern void __early_init(void); ++extern void matrix_init_pins(void); ++ ++void palcallback_cb(uint8_t line) __attribute__((weak)); ++void palcallback_cb(uint8_t line) {} ++ ++void palcallback(void *arg) { ++ uint8_t line = (uint32_t)arg & 0xFF; ++ ++ switch (line) { ++#ifndef LPWR_UART_WAKEUP_DISABLE ++ case PAL_PAD(UART_RX_PIN): { ++ lpwr_set_sleep_wakeupcd(LPWR_WAKEUP_UART); ++ } break; ++#endif ++ default: { ++ lpwr_set_sleep_wakeupcd(LPWR_WAKEUP_MATRIX); ++ } break; ++ } ++ ++ palcallback_cb(line); ++ ++ irqDeinit(); ++ EXTI->PR = 0xFFFFFFFF; ++} ++ ++void pal_events_init(void) { ++ ++ for (uint8_t i = 0; i < 16; i++) { ++ _pal_events[i].cb = palcallback; ++ _pal_events[i].arg = (void *)(uint32_t)i; ++ } ++} ++ ++void lpwr_exti_init_hook(void) __attribute__((weak)); ++void lpwr_exti_init_hook(void) {} ++ ++void lpwr_exti_init(void) { ++ ++ pal_events_init(); ++ ++#if DIODE_DIRECTION == ROW2COL ++ for (uint8_t i = 0; i < ARRAY_SIZE(col_pins); i++) { ++ if (col_pins[i] != NO_PIN) { ++ gpio_set_pin_output_open_drain(col_pins[i]); ++ gpio_write_pin_low(col_pins[i]); ++ } ++ } ++ ++ for (uint8_t i = 0; i < ARRAY_SIZE(row_pins); i++) { ++ if (row_pins[i] != NO_PIN) { ++ gpio_set_pin_input_high(row_pins[i]); ++ waitInputPinDelay(); ++ palEnableLineEvent(row_pins[i], PAL_EVENT_MODE_BOTH_EDGES); ++ } ++ } ++#elif DIODE_DIRECTION == COL2ROW ++ for (uint8_t i = 0; i < ARRAY_SIZE(row_pins); i++) { ++ if (row_pins[i] != NO_PIN) { ++ gpio_set_pin_output_open_drain(row_pins[i]); ++ gpio_write_pin_low(row_pins[i]); ++ } ++ } ++ ++ for (uint8_t i = 0; i < ARRAY_SIZE(col_pins); i++) { ++ if (col_pins[i] != NO_PIN) { ++ gpio_set_pin_input_high(col_pins[i]); ++ waitInputPinDelay(); ++ palEnableLineEvent(col_pins[i], PAL_EVENT_MODE_BOTH_EDGES); ++ } ++ } ++#endif ++ ++#ifndef LPWR_UART_WAKEUP_DISABLE ++ gpio_set_pin_input(UART_RX_PIN); ++ waitInputPinDelay(); ++ palEnableLineEvent(UART_RX_PIN, PAL_EVENT_MODE_BOTH_EDGES); ++#endif ++ ++ lpwr_exti_init_hook(); ++ ++ /* IRQ subsystem initialization.*/ ++ irqInit(); ++} ++ ++void lpwr_clock_enable_user(void) __attribute__((weak)); ++void lpwr_clock_enable_user(void) {} ++ ++void lpwr_clock_enable(void) { ++ ++ __early_init(); ++ ++ rccEnableEXTI(); ++ ++#if WB32_SERIAL_USE_UART1 ++ rccEnableUART1(); ++#endif ++#if WB32_SERIAL_USE_UART2 ++ rccEnableUART2(); ++#endif ++#if WB32_SERIAL_USE_UART3 ++ rccEnableUART3(); ++#endif ++#if WB32_SPI_USE_QSPI ++ rccEnableQSPI(); ++#endif ++#if WB32_SPI_USE_SPIM2 ++ rccEnableSPIM2(); ++#endif ++#if WB32_I2C_USE_I2C1 ++ rccEnableI2C1(); ++#endif ++#if WB32_I2C_USE_I2C2 ++ rccEnableI2C2(); ++#endif ++#if WB32_GPT_USE_TIM1 || WB32_ICU_USE_TIM1 || WB32_PWM_USE_TIM1 ++ rccEnableTIM1(); ++#endif ++#if WB32_ST_USE_TIM2 || WB32_GPT_USE_TIM2 || WB32_ICU_USE_TIM2 || WB32_PWM_USE_TIM2 ++ rccEnableTIM2(); ++#endif ++#if WB32_ST_USE_TIM3 || WB32_GPT_USE_TIM3 || WB32_ICU_USE_TIM3 || WB32_PWM_USE_TIM3 ++ rccEnableTIM3(); ++#endif ++#if WB32_ST_USE_TIM4 || WB32_GPT_USE_TIM4 || WB32_ICU_USE_TIM4 || WB32_PWM_USE_TIM4 ++ rccEnableTIM4(); ++#endif ++ ++#ifndef LPWR_UART_WAKEUP_DISABLE ++ palSetLineMode(UART_RX_PIN, PAL_MODE_ALTERNATE(UART_RX_PAL_MODE) | PAL_OUTPUT_TYPE_PUSHPULL | PAL_OUTPUT_SPEED_HIGHEST); ++#endif ++ ++ lpwr_clock_enable_user(); ++} ++ ++void wb32_stop_mode(void) { ++ ++ SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; ++ ++ /* Prevent the chip from being unable to enter stop mode due to pending interrupts */ ++#if 1 ++ EXTI->PR = 0x7FFFF; ++ for (uint8_t i = 0; i < 8; i++) { ++ for (uint8_t j = 0; j < 32; j++) { ++ if (NVIC->ISPR[i] & (0x01UL < j)) { ++ NVIC->ICPR[i] = (0x01UL < j); ++ } ++ } ++ } ++ SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk; // Clear Systick IRQ Pending ++#endif ++ ++ /* Clear all bits except DBP and FCLKSD bit */ ++ PWR->CR0 &= 0x09U; ++ ++ // STOP LP4 MODE S32KON ++ PWR->CR0 |= 0x3B004U; ++ PWR->CFGR = 0x3B3; ++ ++ PRE_LP(); ++ ++ /* Set SLEEPDEEP bit of Cortex System Control Register */ ++ SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; ++ ++ /* Request Wait For Interrupt */ ++ __WFI(); ++ ++ POST_LP(); ++ ++ /* Clear SLEEPDEEP bit of Cortex System Control Register */ ++ SCB->SCR &= (~SCB_SCR_SLEEPDEEP_Msk); ++ ++ SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; ++} ++ ++void mcu_stop_mode(void) { ++ ++ wb32_stop_mode(); ++} +diff --git a/keyboards/neo/wireless/md_raw.c b/keyboards/neo/wireless/md_raw.c +new file mode 100644 +index 0000000..eebfc94 +--- /dev/null ++++ b/keyboards/neo/wireless/md_raw.c +@@ -0,0 +1,30 @@ ++// Copyright 2024 QMK ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#if RAW_ENABLE ++ ++# include "quantum.h" ++# include "wireless.h" ++# include "usb_endpoints.h" ++# include "usb_main.h" ++ ++void replaced_hid_send(uint8_t *data, uint8_t length) { ++ ++ if (length != RAW_EPSIZE) { ++ return; ++ } ++ ++ if (get_transport() == TRANSPORT_USB) { ++ send_report(USB_ENDPOINT_IN_RAW, data, length); ++ } else { ++ md_send_raw(data, length); ++ } ++} ++ ++void md_receive_raw_cb(uint8_t *data, uint8_t length) { ++ void raw_hid_receive(uint8_t * data, uint8_t length); ++ ++ raw_hid_receive(data, length); ++} ++ ++#endif +diff --git a/keyboards/neo/wireless/md_raw.h b/keyboards/neo/wireless/md_raw.h +new file mode 100644 +index 0000000..9ec762b +--- /dev/null ++++ b/keyboards/neo/wireless/md_raw.h +@@ -0,0 +1,10 @@ ++// Copyright 2024 QMK ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#pragma once ++ ++#define RENAME_WITH_LINE(A, B) COMBINE(A, B) ++#define COMBINE(A, B) A##B ++#define raw_hid_send(a, b) RENAME_WITH_LINE(_temp_rhs_, __LINE__)(a, b) ++#define _temp_rhs_29 replaced_hid_send // raw_hid.h ++#define _temp_rhs_464 replaced_hid_send // via.c +diff --git a/keyboards/neo/wireless/module.c b/keyboards/neo/wireless/module.c +new file mode 100644 +index 0000000..78f98ff +--- /dev/null ++++ b/keyboards/neo/wireless/module.c +@@ -0,0 +1,530 @@ ++// Copyright 2024 Su (@isuua) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include "quantum.h" ++#include "module.h" ++#include "smsg.h" ++#include "uart.h" ++ ++#ifndef MD_BAUD_RATE ++# define MD_BAUD_RATE 115200 ++#endif ++ ++#ifndef MD_SNED_PKT_TIMEOUT ++# define MD_SNED_PKT_TIMEOUT 10 ++#endif ++ ++#ifndef MD_SEND_PKT_RETRY ++# define MD_SEND_PKT_RETRY 40 ++#endif ++ ++#ifndef MD_SEND_PKT_PAYLOAD_MAX ++# define MD_SEND_PKT_PAYLOAD_MAX ((MD_RAW_SIZE) + 4) ++#endif ++ ++#ifndef MD_BT1_NAME ++# define MD_BT1_NAME PRODUCT " BT1" ++#endif ++ ++#ifndef MD_BT2_NAME ++# define MD_BT2_NAME PRODUCT " BT2" ++#endif ++ ++#ifndef MD_BT3_NAME ++# define MD_BT3_NAME PRODUCT " BT3" ++#endif ++ ++#ifndef MD_BT4_NAME ++# define MD_BT4_NAME PRODUCT " BT4" ++#endif ++ ++#ifndef MD_BT5_NAME ++# define MD_BT5_NAME PRODUCT " BT5" ++#endif ++ ++#ifndef MD_DONGLE_MANUFACTURER ++# define MD_DONGLE_MANUFACTURER MANUFACTURER ++#endif ++ ++#ifndef MD_DONGLE_PRODUCT ++# define MD_DONGLE_PRODUCT PRODUCT " Dongle" ++#endif ++ ++#ifndef MD_RAW_SIZE ++# define MD_RAW_SIZE 32 ++#endif ++ ++#define USBCONCAT(a, b) a##b ++#define USBSTR(s) USBCONCAT(L, s) ++ ++typedef struct ++{ ++ uint8_t state; ++ uint8_t indicator; ++ uint8_t version; ++ uint8_t bat; ++} md_info_t; ++ ++static uint8_t md_pkt_payload[MD_SEND_PKT_PAYLOAD_MAX] = {0}; ++static uint8_t md_rev_payload[MD_SEND_PKT_PAYLOAD_MAX] = {0}; ++static uint8_t md_raw_payload[MD_RAW_SIZE] = {0}; ++ ++static md_info_t md_info = { ++ .bat = 100, ++ .indicator = 0, ++ .version = 0, ++ .state = MD_STATE_NONE, ++}; ++ ++static void md_send_ack(void) { ++ ++ uint8_t sdata[0x03] = {0x61, 0x0D, 0x0A}; ++ uart_transmit(sdata, sizeof(sdata)); ++} ++ ++static bool md_check_sum(const uint8_t *data, uint32_t length) { ++ uint8_t sum = 0; ++ ++ for (uint32_t i = 0; i < (length - 1); i++) { ++ sum += data[i]; ++ } ++ ++ return sum == data[length - 1]; ++} ++ ++static void md_calc_check_sum(uint8_t *data, uint32_t length) { ++ uint8_t sum = 0; ++ ++ for (uint32_t i = 0; i < length; i++) { ++ sum += data[i]; ++ } ++ ++ data[length] = sum; ++} ++ ++bool md_receive_process_user(uint8_t *pdata, uint8_t len) __attribute__((weak)); ++bool md_receive_process_user(uint8_t *pdata, uint8_t len) { ++ return true; ++} ++ ++bool md_receive_process_kb(uint8_t *pdata, uint8_t len) __attribute__((weak)); ++bool md_receive_process_kb(uint8_t *pdata, uint8_t len) { ++ return md_receive_process_user(pdata, len); ++} ++ ++void md_receive_raw_cb(uint8_t *pdata, uint8_t len) __attribute__((weak)); ++void md_receive_raw_cb(uint8_t *pdata, uint8_t len) {} ++ ++void md_receive_host_cb(bool resume) __attribute__((weak)); ++void md_receive_host_cb(bool resume) {} ++ ++static void md_receive_msg_task(void) { ++ static uint32_t data_count = 0x00; ++ static uint8_t data_remain = 0x00; ++ ++ while (uart_available()) { ++ uint8_t data = uart_read(); ++ ++ switch (data_count) { ++ case 0: { // cmd ++ switch (data) { ++ case MD_REV_CMD_INDICATOR: ++ case MD_REV_CMD_DEVCTRL: ++ case MD_REV_CMD_BATVOL: ++ case MD_REV_CMD_MD_FW_VERSION: ++ case MD_REV_CMD_HOST_STATE: ++ case 0x61: { ++ md_rev_payload[data_count++] = data; ++ data_remain = 2; ++ } break; ++ case MD_REV_CMD_RAW: { ++ md_rev_payload[data_count++] = data; ++ data_remain = 33; ++ } break; ++ default: { ++ data_count = 0; ++ } break; ++ } ++ continue; ++ } break; ++ case 1: { ++ md_rev_payload[data_count++] = data; ++ data_remain--; ++ continue; ++ } break; ++ case 2: { ++ // ACK ++ if ((md_rev_payload[0] == 0x61) && (md_rev_payload[1] == 0x0D) && (data == 0x0A)) { ++ if (smsg_get_state() == smsg_state_busy) { ++ smsg_set_state(smsg_state_replied); ++ } ++ data_count = 0; ++ return; ++ } ++ } ++ default: { ++ md_rev_payload[data_count++] = data; ++ data_remain--; ++ ++ if (data_remain) { ++ continue; ++ } ++ } break; ++ } ++ ++ if (md_check_sum(md_rev_payload, data_count)) { ++ md_send_ack(); ++ ++ if (md_receive_process_kb(md_rev_payload, data_count) != true) { ++ return; ++ } ++ ++ switch (md_rev_payload[0]) { ++ case MD_REV_CMD_RAW: { ++ uint8_t *pdata; ++ pdata = &md_rev_payload[1]; ++ ++ memcpy(md_raw_payload, pdata, 32); ++ md_receive_raw_cb(md_raw_payload, 32); ++ ++ } break; ++ case MD_REV_CMD_INDICATOR: { ++ md_info.indicator = md_rev_payload[1]; ++ } break; ++ case MD_REV_CMD_DEVCTRL: { ++ switch (md_rev_payload[1]) { ++ case MD_REV_CMD_DEVCTRL_PAIRING: { ++ md_info.state = MD_STATE_PAIRING; ++ } break; ++ case MD_REV_CMD_DEVCTRL_CONNECTED: { ++ md_info.state = MD_STATE_CONNECTED; ++ } break; ++ case MD_REV_CMD_DEVCTRL_DISCONNECTED: { ++ md_info.state = MD_STATE_DISCONNECTED; ++ } break; ++ case MD_REV_CMD_DEVCTRL_REJECT: { ++ md_info.state = MD_STATE_REJECT; ++ } break; ++ default: ++ break; ++ } ++ } break; ++ case MD_REV_CMD_BATVOL: { ++ md_info.bat = md_rev_payload[1]; ++ } break; ++ case MD_REV_CMD_MD_FW_VERSION: { ++ md_info.version = md_rev_payload[1]; ++ } break; ++ case MD_REV_CMD_HOST_STATE: { ++ md_receive_host_cb(md_rev_payload[1] == MD_REV_CMD_HOST_STATE_RESUME); ++ } break; ++ default: ++ break; ++ } ++ } ++ data_count = 0; ++ } ++} ++ ++static void md_send_pkt_task(void) { ++ static uint32_t smsg_timer = 0x00; ++ static uint8_t smsg_retry = 0; ++ ++ switch (smsg_get_state()) { ++ case smsg_state_busy: { ++ if (sync_timer_elapsed32(smsg_timer) > (MD_SNED_PKT_TIMEOUT)) { ++ smsg_retry = 0; ++ smsg_set_state(smsg_state_retry); ++ } ++ } break; ++ case smsg_state_retry: { ++ if (++smsg_retry > MD_SEND_PKT_RETRY) { ++ smsg_retry = 0; ++ smsg_pop(); ++ } ++ smsg_set_state(smsg_state_free); ++ } break; ++ case smsg_state_replied: { ++ smsg_pop(); ++ smsg_set_state(smsg_state_free); ++ } // break; ++ case smsg_state_free: { ++ uint32_t size = smsg_peek(md_pkt_payload); ++ if (size) { ++ if (md_send_pkt(md_pkt_payload, size)) { ++ smsg_timer = sync_timer_read32(); ++ smsg_set_state(smsg_state_busy); ++ } else { ++ smsg_set_state(smsg_state_replied); ++ } ++ } ++ } break; ++ default: ++ break; ++ } ++} ++ ++void md_init(void) { ++ ++ uart_init(MD_BAUD_RATE); ++ smsg_init(); ++ ++ memset(md_pkt_payload, 0, sizeof(md_pkt_payload)); ++} ++ ++void md_main_task(void) { ++ ++ md_send_pkt_task(); ++ md_receive_msg_task(); ++} ++ ++uint8_t *md_getp_state(void) { ++ ++ return &md_info.state; ++} ++ ++uint8_t *md_getp_bat(void) { ++ ++ return &md_info.bat; ++} ++ ++uint8_t *md_getp_indicator(void) { ++ ++ return &md_info.indicator; ++} ++ ++uint8_t md_get_version(void) { ++ ++ return md_info.version; ++} ++ ++bool md_send_pkt(uint8_t *data, uint32_t len) { ++ bool retval = true; ++ ++ if (!data || !len) { ++ return false; ++ } ++ ++ // switch (*data) { ++ // case MD_SND_CMD_RAW: { ++ // retval = false; ++ // } break; ++ // default: ++ // break; ++ // } ++ ++ // send ++ uart_transmit(data, len); ++ ++ return retval; ++} ++ ++void md_send_kb(uint8_t *data) { ++ uint8_t sdata[MD_SND_CMD_KB_LEN + 2] = {0x00}; ++ ++ sdata[0] = MD_SND_CMD_SEND_KB; ++ memcpy(&sdata[1], data, sizeof(sdata) - 2); ++ md_calc_check_sum(sdata, sizeof(sdata) - 1); ++ smsg_push(sdata, sizeof(sdata)); ++} ++ ++void md_send_nkro(uint8_t *data) { ++ uint8_t sdata[MD_SND_CMD_NKRO_LEN + 2] = {0x00}; ++ ++ sdata[0] = MD_SND_CMD_SEND_NKRO; ++ memcpy(&sdata[1], data, sizeof(sdata) - 2); ++ md_calc_check_sum(sdata, sizeof(sdata) - 1); ++ smsg_push(sdata, sizeof(sdata)); ++} ++ ++void md_send_consumer(uint8_t *data) { ++ uint8_t sdata[MD_SND_CMD_CONSUMER_LEN + 2] = {0x00}; ++ ++ sdata[0] = MD_SND_CMD_SEND_CONSUMER; ++ memcpy(&sdata[1], data, sizeof(sdata) - 2); ++ md_calc_check_sum(sdata, sizeof(sdata) - 1); ++ smsg_push(sdata, sizeof(sdata)); ++} ++ ++void md_send_system(uint8_t *data) { ++ uint8_t sdata[MD_SND_CMD_SYSTEM_LEN + 2] = {0x00}; ++ ++ sdata[0] = MD_SND_CMD_SEND_SYSTEM; ++ memcpy(&sdata[1], data, sizeof(sdata) - 2); ++ md_calc_check_sum(sdata, sizeof(sdata) - 1); ++ smsg_push(sdata, sizeof(sdata)); ++} ++ ++void md_send_fn(uint8_t *data) { ++ uint8_t sdata[MD_SND_CMD_FN_LEN + 2] = {0x00}; ++ ++ sdata[0] = MD_SND_CMD_SEND_FN; ++ memcpy(&sdata[1], data, sizeof(sdata) - 2); ++ md_calc_check_sum(sdata, sizeof(sdata) - 1); ++ smsg_push(sdata, sizeof(sdata)); ++} ++ ++void md_send_mouse(uint8_t *data) { ++ uint8_t sdata[MD_SND_CMD_MOUSE_LEN + 2] = {0x00}; ++ ++ sdata[0] = MD_SND_CMD_SEND_MOUSE; ++ memcpy(&sdata[1], data, sizeof(sdata) - 2); ++ md_calc_check_sum(sdata, sizeof(sdata) - 1); ++ smsg_push(sdata, sizeof(sdata)); ++} ++ ++void md_send_devinfo(const char *name) { ++ uint8_t sdata[MD_SND_CMD_DEVINFO_LEN + 3] = {0x00}; ++ uint8_t infolen = strlen((const char *)name); ++ ++ if (infolen > MD_SND_CMD_DEVINFO_LEN) { ++ return; ++ } ++ ++ sdata[0] = MD_SND_CMD_SEND_DEVINFO; ++ sdata[1] = infolen; ++ ++ memcpy(&sdata[2], name, infolen); ++ md_calc_check_sum(sdata, infolen + 2); ++ smsg_push(sdata, sizeof(sdata)); ++} ++ ++void md_send_devctrl(uint8_t cmd) { ++ uint8_t sdata[3] = {0x00}; ++ ++ sdata[0] = MD_SND_CMD_DEVCTRL; ++ memcpy(&sdata[1], &cmd, sizeof(sdata) - 2); ++ md_calc_check_sum(sdata, sizeof(sdata) - 1); ++ smsg_push(sdata, sizeof(sdata)); ++} ++ ++void md_send_manufacturer(char *str, uint8_t len) { ++ uint8_t sdata[MD_SND_CMD_MANUFACTURER_LEN + 3] = {0x00}; ++ ++ if (len > MD_SND_CMD_MANUFACTURER_LEN) { ++ return; ++ } ++ ++ sdata[0] = MD_SND_CMD_MANUFACTURER; ++ sdata[1] = len; ++ memcpy(&sdata[2], str, len); ++ md_calc_check_sum(sdata, len + 2); ++ smsg_push(sdata, len + 3); ++} ++ ++void md_send_product(char *str, uint8_t len) { ++ uint8_t sdata[MD_SND_CMD_PRODUCT_LEN + 3] = {0x00}; ++ ++ if (len > MD_SND_CMD_PRODUCT_LEN) { ++ return; ++ } ++ ++ sdata[0] = MD_SND_CMD_PRODUCT; ++ sdata[1] = len; ++ memcpy(&sdata[2], str, len); ++ md_calc_check_sum(sdata, len + 2); ++ smsg_push(sdata, len + 3); ++} ++ ++void md_send_vpid(uint16_t vid, uint16_t pid) { ++ uint8_t sdata[4 + 2] = {0x00}; ++ uint32_t vpid; ++ ++ vpid = (pid << 16) | vid; ++ ++ sdata[0] = MD_SND_CMD_VPID; ++ memcpy(&sdata[1], &vpid, sizeof(vpid)); ++ md_calc_check_sum(sdata, sizeof(sdata) - 1); ++ smsg_push(sdata, sizeof(sdata)); ++} ++ ++void md_send_raw(uint8_t *data, uint8_t length) { ++ uint8_t sdata[MD_RAW_SIZE + 2] = {0x00}; ++ ++ if (length != MD_RAW_SIZE) { ++ return; ++ } ++ ++ sdata[0] = MD_SND_CMD_RAW; ++ memcpy(&sdata[1], data, length); ++ md_calc_check_sum(sdata, sizeof(sdata) - 1); ++ smsg_push(sdata, sizeof(sdata)); ++} ++ ++void md_devs_change(uint8_t devs, bool reset) __attribute__((weak)); ++void md_devs_change(uint8_t devs, bool reset) { ++ ++ switch (devs) { ++ case DEVS_USB: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_USB); ++ } break; ++ case DEVS_2G4: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_2G4); ++ if (reset) { ++ if (md_get_version() < 48) { ++ md_send_manufacturer(MD_DONGLE_MANUFACTURER, strlen(MD_DONGLE_MANUFACTURER)); ++ md_send_product(MD_DONGLE_PRODUCT, strlen(MD_DONGLE_PRODUCT)); ++ } else { // Add Unicode character support starting from v48. ++ md_send_manufacturer((char *)USBSTR(MD_DONGLE_MANUFACTURER), sizeof(USBSTR(MD_DONGLE_MANUFACTURER))); ++ md_send_product((char *)USBSTR(MD_DONGLE_PRODUCT), sizeof(USBSTR(MD_DONGLE_PRODUCT))); ++ } ++ md_send_vpid(VENDOR_ID, PRODUCT_ID); ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); ++ } ++ } break; ++ case DEVS_BT1: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_BT1); ++ if (reset) { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); ++ md_send_devinfo(MD_BT1_NAME); ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); ++ } ++ } break; ++ case DEVS_BT2: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_BT2); ++ if (reset) { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); ++ md_send_devinfo(MD_BT2_NAME); ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); ++ } ++ } break; ++ case DEVS_BT3: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_BT3); ++ if (reset) { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); ++ md_send_devinfo(MD_BT3_NAME); ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); ++ } ++ } break; ++ case DEVS_BT4: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_BT4); ++ if (reset) { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); ++ md_send_devinfo(MD_BT4_NAME); ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); ++ } ++ } break; ++ case DEVS_BT5: { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_BT5); ++ if (reset) { ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); ++ md_send_devinfo(MD_BT5_NAME); ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); ++ } ++ } break; ++ default: ++ break; ++ } ++} ++ ++bool md_inquire_bat(void) { ++ ++ if (smsg_is_busy()) { ++ return false; ++ } ++ ++ md_send_devctrl(MD_SND_CMD_DEVCTRL_INQVOL); ++ ++ return true; ++} +diff --git a/keyboards/neo/wireless/module.h b/keyboards/neo/wireless/module.h +new file mode 100644 +index 0000000..61a8a45 +--- /dev/null ++++ b/keyboards/neo/wireless/module.h +@@ -0,0 +1,122 @@ ++// Copyright 2024 Su (@isuua) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#pragma once ++ ++// device index ++enum { ++ DEVS_USB = 0, ++ DEVS_BT1, ++ DEVS_BT2, ++ DEVS_BT3, ++ DEVS_BT4, ++ DEVS_BT5, ++ DEVS_2G4, ++}; ++ ++enum { ++ MD_STATE_NONE = 0, ++ MD_STATE_PAIRING, ++ MD_STATE_CONNECTED, ++ MD_STATE_DISCONNECTED, ++ MD_STATE_REJECT, ++}; ++ ++enum { ++ MD_SND_CMD_KB_LEN = 8, ++ MD_SND_CMD_NKRO_LEN = 14, ++ MD_SND_CMD_CONSUMER_LEN = 2, ++ MD_SND_CMD_SYSTEM_LEN = 1, ++ MD_SND_CMD_FN_LEN = 1, ++ MD_SND_CMD_MOUSE_LEN = 5, ++ MD_SND_CMD_DEVINFO_LEN = 18, ++ MD_SND_CMD_MANUFACTURER_LEN = 46, ++ MD_SND_CMD_PRODUCT_LEN = 46, ++}; ++ ++enum { ++ /* send report */ ++ MD_SND_CMD_SEND_KB = 0xA1, ++ MD_SND_CMD_SEND_NKRO = 0xA2, ++ MD_SND_CMD_SEND_CONSUMER = 0xA3, ++ MD_SND_CMD_SEND_SYSTEM = 0xA4, ++ MD_SND_CMD_SEND_FN = 0xA5, ++ MD_SND_CMD_SEND_MOUSE = 0xA8, ++ MD_SND_CMD_SEND_DEVINFO = 0xA9, ++ /* Dongle */ ++ MD_SND_CMD_MANUFACTURER = 0xAB, ++ MD_SND_CMD_PRODUCT = 0xAC, ++ MD_SND_CMD_VPID = 0xAD, ++ MD_SND_CMD_RAW = 0x91, ++ /* device ctrl */ ++ MD_SND_CMD_DEVCTRL = 0xA6, ++ MD_SND_CMD_DEVCTRL_USB = 0x11, ++ MD_SND_CMD_DEVCTRL_2G4 = 0x30, ++ MD_SND_CMD_DEVCTRL_BT1 = 0x31, ++ MD_SND_CMD_DEVCTRL_BT2 = 0x32, ++ MD_SND_CMD_DEVCTRL_BT3 = 0x33, ++ MD_SND_CMD_DEVCTRL_BT4 = 0x34, ++ MD_SND_CMD_DEVCTRL_BT5 = 0x35, ++ MD_SND_CMD_DEVCTRL_PAIR = 0x51, ++ MD_SND_CMD_DEVCTRL_CLEAN = 0x52, ++ MD_SND_CMD_DEVCTRL_INQVOL = 0x53, ++ MD_SND_CMD_DEVCTRL_SLEEP_INSTANT = 0x54, // reserved ++ MD_SND_CMD_DEVCTRL_SLEEP_BT_EN = 0x55, // timeout 30min enable in BT mode ++ MD_SND_CMD_DEVCTRL_SLEEP_BT_DIS = 0x56, // timeout 30min disable in BT mode ++ MD_SND_CMD_DEVCTRL_SLEEP_2G4_EN = 0x57, // timeout 30min enable in 2.4G mode ++ MD_SND_CMD_DEVCTRL_SLEEP_2G4_DIS = 0x58, // timeout 30min enable in 2.4G mode ++ MD_SND_CMD_DEVCTRL_RSV_DEBUG = 0x60, // reserved ++ MD_SND_CMD_DEVCTRL_RSV_SLEEP = 0x61, // reserved ++ MD_SND_CMD_DEVCTRL_FORCED_PAIRING_BT = 0x62, // forced pairing, to be used in a factory environment. ++ MD_SND_CMD_DEVCTRL_FORCED_PAIRING_2G4 = 0x63, // forced pairing, to be used in a factory environment. ++ MD_SND_CMD_DEVCTRL_CHARGING = 0x64, // battery power control. ++ MD_SND_CMD_DEVCTRL_CHARGING_STOP = 0x65, // battery power control. ++ MD_SND_CMD_DEVCTRL_CHARGING_DONE = 0x66, // battery power control. ++ MD_SND_CMD_DEVCTRL_FW_VERSION = 0x70, // module fw version. ++ MD_SND_CMD_INVALID_DATA = 0x00, // unused ++}; ++ ++enum { ++ MD_REV_CMD_RAW = 0x81, ++ MD_REV_CMD_INDICATOR = 0x5A, ++ MD_REV_CMD_DEVCTRL = 0x5B, ++ MD_REV_CMD_DEVCTRL_BAT_LOW = 0x21, // unused ++ MD_REV_CMD_DEVCTRL_BAT_PWROFF = 0x22, // unused ++ MD_REV_CMD_DEVCTRL_BAT_NORMAL = 0x23, // unused ++ MD_REV_CMD_DEVCTRL_PAIRING = 0x31, ++ MD_REV_CMD_DEVCTRL_CONNECTED = 0x32, ++ MD_REV_CMD_DEVCTRL_DISCONNECTED = 0x33, ++ MD_REV_CMD_DEVCTRL_DONE = 0x34, // reserved ++ MD_REV_CMD_DEVCTRL_RECONNECT = 0x35, // reserved ++ MD_REV_CMD_DEVCTRL_REJECT = 0x36, ++ MD_REV_CMD_DEVCTRL_UNPAIRED = 0x37, // reserved ++ MD_REV_CMD_DEVCTRL_MD_WAKEUP = 0x42, // unused ++ MD_REV_CMD_DEVCTRL_CLS_UART = 0x43, // unused ++ MD_REV_CMD_BATVOL = 0x5C, ++ MD_REV_CMD_MD_FW_VERSION = 0x5D, ++ MD_REV_CMD_HOST_STATE = 0x60, ++ MD_REV_CMD_HOST_STATE_SUSPEND = 0x00, ++ MD_REV_CMD_HOST_STATE_RESUME = 0x01, ++}; ++ ++void md_init(void); ++void md_main_task(void); ++void md_send_kb(uint8_t *data); ++void md_send_nkro(uint8_t *data); ++void md_send_consumer(uint8_t *data); ++void md_send_system(uint8_t *data); ++void md_send_fn(uint8_t *data); ++void md_send_mouse(uint8_t *data); ++void md_send_devctrl(uint8_t cmd); ++void md_send_manufacturer(char *str, uint8_t len); ++void md_send_product(char *str, uint8_t len); ++void md_send_vpid(uint16_t vid, uint16_t pid); ++void md_send_raw(uint8_t *data, uint8_t length); ++bool md_send_pkt(uint8_t *data, uint32_t len); ++bool md_receive_process_user(uint8_t *pdata, uint8_t len); ++void md_devs_change(uint8_t devs, bool reset); ++bool md_inquire_bat(void); ++uint8_t md_get_version(void); ++uint8_t *md_getp_state(void); ++uint8_t *md_getp_bat(void); ++uint8_t *md_getp_indicator(void); +diff --git a/keyboards/neo/wireless/smsg.c b/keyboards/neo/wireless/smsg.c +new file mode 100644 +index 0000000..cfa9571 +--- /dev/null ++++ b/keyboards/neo/wireless/smsg.c +@@ -0,0 +1,122 @@ ++// Copyright 2024 Su (@isuua) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include "smsg.h" ++#include ++ ++#ifndef SMSG_NUM ++# define SMSG_NUM 40 ++#endif ++ ++#ifndef SMSG_PAYLOAD_LEN ++# define SMSG_PAYLOAD_LEN 50 ++#endif ++ ++#define SMSG_BUF_SIZE (SMSG_NUM * SMSG_PAYLOAD_LEN) ++#define END_PTR ((smsg_ptr_t *)&smsg_instance.ptr[SMSG_NUM - 1]) ++#define FREE_SPACE ((uint32_t)(&smsg_buffer[SMSG_BUF_SIZE - 1] - smsg_instance.buffer)) ++ ++typedef struct { ++ uint8_t *head; ++ uint8_t *tail; ++} smsg_ptr_t; ++ ++typedef struct { ++ smsg_states_t state; ++ smsg_ptr_t *ptr; ++ smsg_ptr_t *in_ptr; ++ smsg_ptr_t *out_ptr; ++ uint8_t *buffer; ++} smsg_t; ++ ++static smsg_ptr_t smsg_ptr[SMSG_NUM]; ++static uint8_t smsg_buffer[SMSG_BUF_SIZE]; ++static smsg_t smsg_instance; ++ ++void smsg_init(void) { ++ ++ smsg_instance.buffer = smsg_buffer; ++ smsg_instance.ptr = smsg_ptr; ++ smsg_instance.ptr->head = smsg_instance.buffer; ++ smsg_instance.ptr->tail = smsg_instance.buffer; ++ smsg_instance.in_ptr = smsg_instance.ptr; ++ smsg_instance.out_ptr = smsg_instance.ptr; ++ smsg_instance.state = smsg_state_free; ++} ++ ++bool smsg_push(uint8_t *buf, uint32_t size) { ++ ++ if (smsg_instance.in_ptr == END_PTR) { ++ if (smsg_instance.ptr == smsg_instance.out_ptr) { ++ return false; ++ } ++ } else { ++ if ((smsg_instance.in_ptr + 1) == smsg_instance.out_ptr) { ++ return false; ++ } ++ } ++ ++ if (FREE_SPACE < SMSG_PAYLOAD_LEN) { ++ smsg_instance.buffer = smsg_buffer; ++ } ++ ++ if (size > SMSG_PAYLOAD_LEN) { ++ return false; ++ } ++ ++ memcpy(smsg_instance.buffer, buf, size); ++ smsg_instance.in_ptr->head = smsg_instance.buffer; ++ smsg_instance.buffer += size; ++ smsg_instance.in_ptr->tail = smsg_instance.buffer; ++ if (smsg_instance.in_ptr == END_PTR) { ++ smsg_instance.in_ptr = smsg_instance.ptr; ++ } else { ++ smsg_instance.in_ptr++; ++ } ++ ++ return true; ++} ++ ++uint32_t smsg_peek(uint8_t *buf) { ++ ++ if (smsg_instance.out_ptr != smsg_instance.in_ptr) { ++ uint32_t size; ++ ++ size = smsg_instance.out_ptr->tail - smsg_instance.out_ptr->head; ++ memcpy(buf, smsg_instance.out_ptr->head, size); ++ ++ return size; ++ } ++ ++ return 0; ++} ++ ++void smsg_pop(void) { ++ ++ if (smsg_instance.out_ptr != smsg_instance.in_ptr) { ++ if (smsg_instance.out_ptr == END_PTR) { ++ smsg_instance.out_ptr = smsg_instance.ptr; ++ } else { ++ smsg_instance.out_ptr++; ++ } ++ } ++} ++ ++smsg_states_t smsg_get_state(void) { ++ ++ return smsg_instance.state; ++} ++ ++void smsg_set_state(smsg_states_t state) { ++ ++ smsg_instance.state = state; ++} ++ ++bool smsg_is_busy(void) { ++ ++ if (smsg_instance.out_ptr != smsg_instance.in_ptr) { ++ return true; ++ } ++ ++ return false; ++} +diff --git a/keyboards/neo/wireless/smsg.h b/keyboards/neo/wireless/smsg.h +new file mode 100644 +index 0000000..ea08630 +--- /dev/null ++++ b/keyboards/neo/wireless/smsg.h +@@ -0,0 +1,22 @@ ++// Copyright 2024 Su (@isuua) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#pragma once ++ ++#include ++#include ++ ++typedef enum { ++ smsg_state_free = 0, ++ smsg_state_busy, ++ smsg_state_retry, ++ smsg_state_replied ++} smsg_states_t; ++ ++void smsg_init(void); ++bool smsg_push(uint8_t *buf, uint32_t size); ++uint32_t smsg_peek(uint8_t *buf); ++void smsg_pop(void); ++smsg_states_t smsg_get_state(void); ++void smsg_set_state(smsg_states_t state); ++bool smsg_is_busy(void); +diff --git a/keyboards/neo/wireless/transport.c b/keyboards/neo/wireless/transport.c +new file mode 100644 +index 0000000..09bb8d5 +--- /dev/null ++++ b/keyboards/neo/wireless/transport.c +@@ -0,0 +1,167 @@ ++// Copyright 2024 Su (@isuua) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include "quantum.h" ++#include "module.h" ++#include "usb_main.h" ++#include "transport.h" ++ ++#ifndef USB_POWER_DOWN_DELAY ++# define USB_POWER_DOWN_DELAY 3000 ++#endif ++ ++extern host_driver_t chibios_driver; ++extern host_driver_t wireless_driver; ++ ++static transport_t transport = TRANSPORT_USB; ++ ++void wls_transport_enable(bool enable) __attribute__((weak)); ++void wls_transport_enable(bool enable) { ++ ++ if (enable) { ++ if (host_get_driver() != &wireless_driver) { ++ host_set_driver(&wireless_driver); ++ usb_device_state_set_protocol(true); //keyboard_protocol = true; // default with true ++ } ++ } else { ++ if (*md_getp_state() == MD_STATE_CONNECTED) { ++ wireless_driver.send_keyboard(NULL); ++ wireless_driver.send_nkro(NULL); ++ } ++ } ++} ++ ++/* Control USB device connection and disconnection by ++ * controlling the power supply of the USB DP pull-up resistor. ++ * Overwrite these two functions. */ ++void usb_power_connect(void) __attribute__((weak)); ++void usb_power_connect(void) {} ++ ++void usb_power_disconnect(void) __attribute__((weak)); ++void usb_power_disconnect(void) {} ++ ++void usb_transport_enable(bool enable) __attribute__((weak)); ++void usb_transport_enable(bool enable) { ++ ++ if (enable) { ++ if (host_get_driver() != &chibios_driver) { ++ extern bool last_suspend_state; ++ ++ /* This flag is not set to 1 with probability after usb restart */ ++ last_suspend_state = true; ++#if !defined(KEEP_USB_CONNECTION_IN_WIRELESS_MODE) ++ usb_power_connect(); ++ restart_usb_driver(&USBD1); ++#endif ++ host_set_driver(&chibios_driver); ++ } ++ } else { ++ if (USB_DRIVER.state == USB_ACTIVE) { ++ report_keyboard_t empty_report = {0}; ++ report_nkro_t empty_nkro_report = {0}; ++ host_keyboard_send(&empty_report); ++ host_nkro_send(&empty_nkro_report); ++ } ++ ++#if !defined(KEEP_USB_CONNECTION_IN_WIRELESS_MODE) ++ usbStop(&USBD1); ++ usbDisconnectBus(&USBD1); ++ usb_power_disconnect(); ++#endif ++ } ++} ++ ++void set_transport(transport_t new_transport) { ++ ++ transport = new_transport; ++ ++ switch (transport) { ++ case TRANSPORT_USB: { ++ usb_transport_enable(true); ++ wls_transport_enable(false); ++ } break; ++ case TRANSPORT_WLS: { ++ wls_transport_enable(true); ++ usb_transport_enable(false); ++ } break; ++ default: ++ break; ++ } ++} ++ ++transport_t get_transport(void) { ++ ++ return transport; ++} ++ ++void usb_remote_wakeup(void) { ++ ++#ifdef USB_REMOTE_USE_QMK ++ if (USB_DRIVER.state == USB_SUSPENDED) { ++ dprintln("suspending keyboard"); ++ while (USB_DRIVER.state == USB_SUSPENDED) { ++ /* Do this in the suspended state */ ++ suspend_power_down(); // on AVR this deep sleeps for 15ms ++ /* Remote wakeup */ ++ if ((USB_DRIVER.status & 2U) && suspend_wakeup_condition()) { ++ usbWakeupHost(&USB_DRIVER); ++# if USB_SUSPEND_WAKEUP_DELAY > 0 ++ // Some hubs, kvm switches, and monitors do ++ // weird things, with USB device state bouncing ++ // around wildly on wakeup, yielding race ++ // conditions that can corrupt the keyboard state. ++ // ++ // Pause for a while to let things settle... ++ wait_ms(USB_SUSPEND_WAKEUP_DELAY); ++# endif ++ } ++ } ++ /* Woken up */ ++ } ++#else ++ static uint32_t suspend_timer = 0x00; ++ ++ if ((USB_DRIVER.state == USB_SUSPENDED)) { ++ if (!suspend_timer) suspend_timer = sync_timer_read32(); ++ if (sync_timer_elapsed32(suspend_timer) >= USB_POWER_DOWN_DELAY) { ++ suspend_timer = 0x00; ++ suspend_power_down(); ++ } ++ } else { ++ suspend_timer = 0x00; ++ } ++#endif ++} ++ ++#ifndef USB_REMOTE_USE_QMK ++void usb_remote_host(void) { ++ ++ if (USB_DRIVER.state == USB_SUSPENDED) { ++ if ((USB_DRIVER.status & 2U) && suspend_wakeup_condition()) { ++ usbWakeupHost(&USB_DRIVER); ++# if USB_SUSPEND_WAKEUP_DELAY > 0 ++ // Some hubs, kvm switches, and monitors do ++ // weird things, with USB device state bouncing ++ // around wildly on wakeup, yielding race ++ // conditions that can corrupt the keyboard state. ++ // ++ // Pause for a while to let things settle... ++ wait_ms(USB_SUSPEND_WAKEUP_DELAY); ++# endif ++ } ++# if !defined(USB_REMOTE_USE_QMK) && USB_POWER_DOWN_DELAY ++ suspend_wakeup_init(); ++# endif ++ } ++} ++ ++bool process_action_kb(keyrecord_t *record) { ++ ++ (void)record; ++ if (get_transport() == TRANSPORT_USB){ ++ usb_remote_host(); ++ } ++ ++ return true; ++} ++#endif +diff --git a/keyboards/neo/wireless/transport.h b/keyboards/neo/wireless/transport.h +new file mode 100644 +index 0000000..e6ff663 +--- /dev/null ++++ b/keyboards/neo/wireless/transport.h +@@ -0,0 +1,18 @@ ++// Copyright 2024 Su (@isuua) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#pragma once ++ ++typedef enum { ++ TRANSPORT_NONE, ++ TRANSPORT_USB, ++ TRANSPORT_WLS, ++} transport_t; ++ ++void wls_transport_enable(bool enable); ++void usb_transport_enable(bool enable); ++void set_transport(transport_t new_transport); ++transport_t get_transport(void); ++void usb_power_connect(void); ++void usb_power_disconnect(void); ++void usb_remote_wakeup(void); +diff --git a/keyboards/neo/wireless/wireless.c b/keyboards/neo/wireless/wireless.c +new file mode 100644 +index 0000000..5358b9e +--- /dev/null ++++ b/keyboards/neo/wireless/wireless.c +@@ -0,0 +1,266 @@ ++ ++// Copyright 2024 Su (@isuua) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include "quantum.h" ++#include "wireless.h" ++ ++#ifndef WLS_INQUIRY_BAT_TIME ++# define WLS_INQUIRY_BAT_TIME 3000 ++#endif ++ ++#ifndef WLS_KEYBOARD_REPORT_KEYS ++# define WLS_KEYBOARD_REPORT_KEYS KEYBOARD_REPORT_KEYS ++#endif ++ ++static uint8_t wls_devs = DEVS_USB; ++ ++void last_matrix_activity_trigger(void); ++ ++uint8_t wireless_keyboard_leds(void); ++void wireless_send_keyboard(report_keyboard_t *report); ++void wireless_send_nkro(report_nkro_t *report); ++void wireless_send_mouse(report_mouse_t *report); ++void wireless_send_extra(report_extra_t *report); ++ ++host_driver_t wireless_driver = { ++ .keyboard_leds = wireless_keyboard_leds, ++ .send_keyboard = wireless_send_keyboard, ++ .send_nkro = wireless_send_nkro, ++ .send_mouse = wireless_send_mouse, ++ .send_extra = wireless_send_extra, ++}; ++ ++void wireless_init(void) { ++ ++ md_init(); ++} ++ ++uint8_t wireless_keyboard_leds(void) __attribute__((weak)); ++uint8_t wireless_keyboard_leds(void) { ++ ++ if (*md_getp_state() == MD_STATE_CONNECTED) { ++ return *md_getp_indicator(); ++ } ++ ++ return 0; ++} ++ ++void wireless_send_keyboard(report_keyboard_t *report) __attribute__((weak)); ++void wireless_send_keyboard(report_keyboard_t *report) { ++ uint8_t wls_report_kb[MD_SND_CMD_KB_LEN] = {0}; ++ ++ if (*md_getp_state() != MD_STATE_CONNECTED) { ++ wireless_devs_change(wls_devs, wls_devs, false); ++ return; ++ } ++ ++ _Static_assert((MD_SND_CMD_KB_LEN) - (WLS_KEYBOARD_REPORT_KEYS) >= 2, "WLS_KEYBOARD_REPORT_KEYS cannot be greater than MD_SND_CMD_KB_LEN - 2."); ++ ++ if (report != NULL) { ++ memcpy(wls_report_kb, (uint8_t *)&report->mods, WLS_KEYBOARD_REPORT_KEYS + 2); ++ } ++ ++ md_send_kb(wls_report_kb); ++} ++ ++void wireless_send_nkro(report_nkro_t *report) __attribute__((weak)); ++void wireless_send_nkro(report_nkro_t *report) { ++ static report_keyboard_t temp_report_keyboard = {0}; ++ uint8_t wls_report_nkro[MD_SND_CMD_NKRO_LEN] = {0}; ++ ++#ifdef NKRO_ENABLE ++ if (*md_getp_state() != MD_STATE_CONNECTED) { ++ wireless_devs_change(wls_devs, wls_devs, false); ++ return; ++ } ++ ++ if (report != NULL) { ++ report_nkro_t temp_report_nkro = *report; ++ uint8_t key_count = 0; ++ ++ temp_report_keyboard.mods = temp_report_nkro.mods; ++ for (uint8_t i = 0; i < NKRO_REPORT_BITS; i++) { ++ key_count += __builtin_popcount(temp_report_nkro.bits[i]); ++ } ++ ++ // find key up and del it. ++ uint8_t nkro_keys = key_count; ++ for (uint8_t i = 0; i < WLS_KEYBOARD_REPORT_KEYS && temp_report_keyboard.keys[i]; i++) { ++ report_nkro_t found_report_nkro; ++ uint8_t usageid = 0x00; ++ uint8_t n; ++ ++ found_report_nkro = temp_report_nkro; ++ ++ for (uint8_t c = 0; c < nkro_keys; c++) { ++ for (n = 0; n < NKRO_REPORT_BITS && !found_report_nkro.bits[n]; n++) {} ++ usageid = (n << 3) | biton(found_report_nkro.bits[n]); ++ del_key_bit(&found_report_nkro, usageid); ++ if (usageid == temp_report_keyboard.keys[i]) { ++ del_key_bit(&temp_report_nkro, usageid); ++ nkro_keys--; ++ break; ++ } ++ } ++ ++ if (usageid != temp_report_keyboard.keys[i]) { ++ temp_report_keyboard.keys[i] = 0x00; ++ } ++ } ++ ++ /* ++ * Use NKRO for sending when more than 6 keys are pressed ++ * to solve the issue of the lack of a protocol flag in wireless mode. ++ */ ++ ++ temp_report_nkro = *report; ++ ++ for (uint8_t i = 0; i < key_count; i++) { ++ uint8_t usageid; ++ uint8_t idx, n = 0; ++ ++ for (n = 0; n < NKRO_REPORT_BITS && !temp_report_nkro.bits[n]; n++) {} ++ usageid = (n << 3) | biton(temp_report_nkro.bits[n]); ++ del_key_bit(&temp_report_nkro, usageid); ++ ++ for (idx = 0; idx < WLS_KEYBOARD_REPORT_KEYS; idx++) { ++ if (temp_report_keyboard.keys[idx] == usageid) { ++ break; ++ } ++ if (temp_report_keyboard.keys[idx] == 0x00) { ++ temp_report_keyboard.keys[idx] = usageid; ++ break; ++ } ++ } ++ ++ if (idx == WLS_KEYBOARD_REPORT_KEYS && (usageid < (MD_SND_CMD_NKRO_LEN * 8))) { ++ wls_report_nkro[usageid / 8] |= 0x01 << (usageid % 8); ++ } ++ } ++ } else { ++ memset(&temp_report_keyboard, 0, sizeof(temp_report_keyboard)); ++ } ++#endif ++ ++ wireless_driver.send_keyboard(&temp_report_keyboard); ++ md_send_nkro(wls_report_nkro); ++} ++ ++void wireless_send_mouse(report_mouse_t *report) __attribute__((weak)); ++void wireless_send_mouse(report_mouse_t *report) { ++ typedef struct { ++ uint8_t buttons; ++ int8_t x; ++ int8_t y; ++ int8_t v; ++ int8_t h; ++ } __attribute__((packed)) wls_report_mouse_t; ++ ++ wls_report_mouse_t wls_report_mouse = {0}; ++ ++ if (*md_getp_state() != MD_STATE_CONNECTED) { ++ wireless_devs_change(wls_devs, wls_devs, false); ++ return; ++ } ++ ++ if (report != NULL) { ++ wls_report_mouse.buttons = report->buttons; ++ wls_report_mouse.x = report->x; ++ wls_report_mouse.y = report->y; ++ wls_report_mouse.v = report->v; ++ wls_report_mouse.h = report->h; ++ } ++ ++ md_send_mouse((uint8_t *)&wls_report_mouse); ++} ++ ++void wireless_send_extra(report_extra_t *report) __attribute__((weak)); ++void wireless_send_extra(report_extra_t *report) { ++ uint16_t usage = 0; ++ ++ if (*md_getp_state() != MD_STATE_CONNECTED) { ++ wireless_devs_change(wls_devs, wls_devs, false); ++ return; ++ } ++ ++ if (report != NULL) { ++ usage = report->usage; ++ ++ switch (usage) { ++ case 0x81: ++ case 0x82: ++ case 0x83: { // system usage ++ usage = 0x01 << (usage - 0x81); ++ md_send_system((uint8_t *)&usage); ++ } break; ++ default: { ++ md_send_consumer((uint8_t *)&usage); ++ } break; ++ } ++ } ++} ++ ++void wireless_devs_change_user(uint8_t old_devs, uint8_t new_devs, bool reset) __attribute__((weak)); ++void wireless_devs_change_user(uint8_t old_devs, uint8_t new_devs, bool reset) {} ++ ++void wireless_devs_change_kb(uint8_t old_devs, uint8_t new_devs, bool reset) __attribute__((weak)); ++void wireless_devs_change_kb(uint8_t old_devs, uint8_t new_devs, bool reset) {} ++ ++void wireless_devs_change(uint8_t old_devs, uint8_t new_devs, bool reset) { ++ bool changed = (old_devs == DEVS_USB) ? (new_devs != DEVS_USB) : (new_devs == DEVS_USB); ++ ++ if (changed) { ++ set_transport((new_devs != DEVS_USB) ? TRANSPORT_WLS : TRANSPORT_USB); ++ } ++ ++ if ((wls_devs != new_devs) || reset) { ++ *md_getp_state() = MD_STATE_DISCONNECTED; ++ *md_getp_indicator() = 0; ++ } ++ ++ wls_devs = new_devs; ++ last_matrix_activity_trigger(); ++ ++ md_devs_change(new_devs, reset); ++ wireless_devs_change_kb(old_devs, new_devs, reset); ++ wireless_devs_change_user(old_devs, new_devs, reset); ++} ++ ++uint8_t wireless_get_current_devs(void) { ++ return wls_devs; ++} ++ ++void wireless_pre_task(void) __attribute__((weak)); ++void wireless_pre_task(void) {} ++ ++void wireless_post_task(void) __attribute__((weak)); ++void wireless_post_task(void) {} ++ ++void wireless_task(void) { ++ ++ wireless_pre_task(); ++ lpwr_task(); ++ md_main_task(); ++ wireless_post_task(); ++ ++ /* usb_remote_wakeup() should be invoked last so that we have chance ++ * to switch to wireless after start-up when usb is not connected ++ */ ++ if (get_transport() == TRANSPORT_USB) { ++ usb_remote_wakeup(); ++ } else if (lpwr_get_state() == LPWR_NORMAL) { ++ static uint32_t inqtimer = 0x00; ++ ++ if (sync_timer_elapsed32(inqtimer) >= (WLS_INQUIRY_BAT_TIME)) { ++ if (md_inquire_bat()) { ++ inqtimer = sync_timer_read32(); ++ } ++ } ++ } ++} ++ ++void housekeeping_task_kb(void) { ++ ++ wireless_task(); ++} +diff --git a/keyboards/neo/wireless/wireless.h b/keyboards/neo/wireless/wireless.h +new file mode 100644 +index 0000000..683347b +--- /dev/null ++++ b/keyboards/neo/wireless/wireless.h +@@ -0,0 +1,14 @@ ++// Copyright 2024 Su (@isuua) ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#pragma once ++ ++#include "transport.h" ++#include "lowpower.h" ++#include "module.h" ++ ++void wireless_init(void); ++void wireless_devs_change(uint8_t old_devs, uint8_t new_devs, bool reset); ++uint8_t wireless_get_current_devs(void); ++void wireless_pre_task(void); ++void wireless_post_task(void); +diff --git a/keyboards/neo/wireless/wireless.mk b/keyboards/neo/wireless/wireless.mk +new file mode 100644 +index 0000000..5076e54 +--- /dev/null ++++ b/keyboards/neo/wireless/wireless.mk +@@ -0,0 +1,26 @@ ++WIRELESS_ENABLE ?= yes ++WIRELESS_DIR = $(TOP_DIR)/keyboards/neo/wireless ++ ++ifeq ($(strip $(WIRELESS_ENABLE)), yes) ++ OPT_DEFS += -DWIRELESS_ENABLE -DNO_USB_STARTUP_CHECK ++ ++ OPT_DEFS += -include $(WIRELESS_DIR)/md_raw.h ++ ++ UART_DRIVER_REQUIRED ?= yes ++ WIRELESS_LPWR_STOP_ENABLE ?= yes ++ ++ VPATH += $(WIRELESS_DIR) ++ ++ SRC += \ ++ $(WIRELESS_DIR)/wireless.c \ ++ $(WIRELESS_DIR)/transport.c \ ++ $(WIRELESS_DIR)/lowpower.c \ ++ $(WIRELESS_DIR)/md_raw.c \ ++ $(WIRELESS_DIR)/smsg.c \ ++ $(WIRELESS_DIR)/module.c ++ ++ ifeq ($(strip $(WIRELESS_LPWR_STOP_ENABLE)), yes) ++ OPT_DEFS += -DWIRELESS_LPWR_STOP_ENABLE ++ SRC += $(WIRELESS_DIR)/lpwr_wb32.c ++ endif ++endif diff --git a/qmk.json b/qmk.json index fb02415..ded9515 100644 --- a/qmk.json +++ b/qmk.json @@ -2,7 +2,7 @@ "userspace_version": "1.1", "build_targets": [ ["mode/m256wh", "mirage"], - ["neo/neo65", "via"], + ["neo/neo65_trimode", "default"], ["cannonkeys/db60/hotswap", "milky_neko"], ["dztech/dz60rgb/v2_1", "silent_tofu"] ] diff --git a/scripts/regen-wireless-patch.sh b/scripts/regen-wireless-patch.sh new file mode 100755 index 0000000..b4b6642 --- /dev/null +++ b/scripts/regen-wireless-patch.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Regenerate patches/0002-add-edthu-wireless-support.patch from the vendored +# source in vendor/edthu-wireless/. The patch is purely additive (all new +# files), so it applies cleanly on top of the pinned upstream qmk_firmware. +# +# Usage: +# scripts/regen-wireless-patch.sh +# +# Override the pristine upstream checkout used as the diff baseline with: +# QMK_PRISTINE=/path/to/qmk_firmware scripts/regen-wireless-patch.sh +# +# Otherwise the pinned pristine source from flake.nix is resolved via +# `nix build .#qmkFirmwareSrc`. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +VENDOR="$REPO_ROOT/vendor/edthu-wireless" +PATCH="$REPO_ROOT/patches/0002-add-edthu-wireless-support.patch" + +if [ ! -d "$VENDOR/keyboards/neo/neo65_trimode" ]; then + echo "error: vendored source not found at $VENDOR" >&2 + echo " see $REPO_ROOT/vendor/edthu-wireless/README.md" >&2 + exit 1 +fi + +if ! command -v git >/dev/null; then + echo "error: git is required to generate the patch" >&2 + exit 1 +fi + +# Resolve a pristine upstream checkout at the pinned rev. Prefer an explicit +# override; otherwise pull the fetchFromGitHub output out of the nix store via +# the flake's exposed package (no git clone needed). +if [ -n "${QMK_PRISTINE:-}" ]; then + PRISTINE="$QMK_PRISTINE" +else + echo "resolving pristine firmware via nix build .#qmkFirmwareSrc ..." + PRISTINE="$(nix build "$REPO_ROOT#qmkFirmwareSrc" --no-link --print-out-paths)" +fi +echo "pristine: $PRISTINE" + +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +# 1. Copy pristine -> workdir and make it writable. +cp -r "$PRISTINE" "$WORK/src" +chmod -R u+w "$WORK/src" + +# 2. Commit the pristine baseline so the diff captures ONLY what the overlay +# adds (and we get diff --git a/... b/... new-file format that patch -p1 +# understands). +( cd "$WORK/src" && git init -q \ + && git -c user.email=keebs@local -c user.name=keebs add -A \ + && git -c user.email=keebs@local -c user.name=keebs commit -qm pristine ) + +# 3. Overlay the vendored firmware tree onto the firmware checkout. Copy +# ONLY vendor/.../keyboards/ (not the vendor README.md) -- otherwise on a +# case-insensitive filesystem README.md would shadow upstream's lowercase +# readme.md and produce a spurious patch hunk that fails to apply. Target +# src/keyboards/ so the patch paths come out as keyboards/neo/... (patch -p1 +# then strips the keyboards/ prefix correctly). +mkdir -p "$WORK/src/keyboards" +cp -r "$VENDOR/keyboards/." "$WORK/src/keyboards/" + +# 4. Stage and emit the patch (additions only vs the pristine baseline). +# Explicit a// b/ prefixes so `patch -p1` (used by nix applyPatches) strips +# exactly one component and the files land at keyboards/neo/... in the +# firmware tree. Without --src-prefix/--dst-prefix, git emits +# `keyboards/neo/...` and -p1 would wrongly strip `keyboards/`. +( cd "$WORK/src" \ + && git -c user.email=keebs@local -c user.name=keebs add -A \ + && git -c diff.mnemonicprefix=false diff --cached --binary \ + --src-prefix=a/ --dst-prefix=b/ > "$PATCH" ) + +echo "wrote $PATCH" + +# 5. Verify the patch applies cleanly on a fresh pristine checkout AND that the +# ne/wireless files land at keyboards/neo/wireless (not neo/wireless). A +# plain --dry-run passes for new files even at the wrong path, so we do a +# real apply and then assert the expected files exist. Errors stay visible +# per repo convention -- no 2>/dev/null silencing. +VERIFY="$(mktemp -d)" +trap 'rm -rf "$WORK" "$VERIFY"' EXIT +cp -r "$PRISTINE" "$VERIFY/src" +chmod -R u+w "$VERIFY/src" +( cd "$VERIFY/src" && patch -p1 < "$PATCH" >/dev/null ) \ + || { echo "verify: patch FAILED to apply" >&2; exit 1; } +for f in keyboards/neo/neo65_trimode/neo65_trimode.c \ + keyboards/neo/wireless/wireless.c \ + keyboards/neo/wireless/wireless.mk; do + [ -f "$VERIFY/src/$f" ] || { echo "verify: MISSING $f (patch paths wrong)" >&2; exit 1; } +done +echo "verify: patch applies cleanly, files land at keyboards/neo/" + +echo +echo "done. run: direnv reload && qmk compile -kb neo/neo65_trimode -km default" diff --git a/vendor/edthu-wireless/README.md b/vendor/edthu-wireless/README.md new file mode 100644 index 0000000..87935cc --- /dev/null +++ b/vendor/edthu-wireless/README.md @@ -0,0 +1,63 @@ +# vendor/edthu-wireless -- vendored wireless support for WB32 + CH582F boards + +Source: https://github.com/edthu/qmk_firmware branch `wireless` +Upstream commit vendored: 52aae316603b2129feafdc43db5fcda59bec215d +(Add neo ergo layout, 2025-11-06) + +The upstream commit edthu branched `wireless` from is `1a58fce043` +(2025-08-06), which is an ancestor of our pinned firmware rev in +`flake.nix` (85886db). So this is purely additive: every file here is a +NEW file relative to upstream QMK. No core quantum/tmk_core files are +modified, so the regenerated patch can't bitrot against upstream changes. + +Layout mirrors upstream-relative paths exactly so the regen script can +overlay vendor/edthu-wireless/ onto a pristine firmware checkout 1:1: + + keyboards/neo/neo65_trimode/ -- the NEO65 tri-mode keyboard definition + keyboards/neo/wireless/ -- shared WB32<->CH582F UART transport stack + +Only the NEO65 is vendored. edthu's branch also ports the Neo70/Neo80/ +NeoErgo, qk65v2classic, and Bridge75; none are copied here. + +## Local modifications to vendored source + +These differ from edthu's upstream `wireless` branch and must survive a +re-sync: + +- `keyboards/neo/wireless/lpwr_wb32.c` and + `keyboards/neo/neo65_trimode/neo65_trimode.c`: QMK renamed the GPIO + helpers on 2024-02-25 (`setPinOutputOpenDrain` -> + `gpio_set_pin_output_open_drain`, `writePinLow` -> + `gpio_write_pin_low`, `setPinInputHigh` -> `gpio_set_pin_input_high`, + `setPinInput` -> `gpio_set_pin_input`, `writePin` -> `gpio_write_pin`). + edthu's source used the pre-rename names in these two files; our pinned + firmware rev only defines the new names, so without these renames they + fail to compile with `-Wimplicit-function-declaration`. Applied via sed + in-place to the vendored files. + +## How to update + +1. Update edthu's commit reference above in this file. +2. `git -C /tmp/edthu/qmk_firmware fetch origin wireless && git checkout ` + (or clone fresh). +3. `rsync -a --delete` the two keyboard dirs into + `vendor/edthu-wireless/keyboards/neo/{neo65_trimode,wireless}/`. +4. Run `scripts/regen-wireless-patch.sh` to regenerate + `patches/0002-add-edthu-wireless-support.patch`. +5. `direnv reload` and build: `qmk compile -kb neo/neo65_trimode -km default`. + +## How the patch is built + +The regen script (scripts/regen-wireless-patch.sh): + +1. Resolves a pristine upstream firmware checkout at the rev pinned in + `flake.nix`, via `nix build .#qmkFirmwareSrc --no-link --print-out-paths` + (or `$QMK_PRISTINE` if set). +2. Copies it to a temp workdir, overlays `vendor/edthu-wireless/` on top. +3. `git add -A && git diff --cached --binary > patches/0002-...patch`. +4. Verifies the patch applies cleanly on the pristine tree with + `patch --dry-run -p1`. + +## License + +GPL-2.0-or-later (QMK firmware; see upstream). No binary blobs. diff --git a/vendor/edthu-wireless/keyboards/neo/neo65_trimode/config.h b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/config.h new file mode 100644 index 0000000..07acb06 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/config.h @@ -0,0 +1,50 @@ +// Copyright 2025 emolitor (github.com/emolitor) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +/* + * Specific tapping term for wireless configuration. If the key is held for + * less than 3 seconds we select that device for the active connection. If + * greater than 3 seconds we select that device and go into pairing mode. + */ +#define WIRELESS_TAPPING_TERM 3000 + +/* LEDS */ +#define LED_BLINK_FAST_PERIOD_MS 300 + + +/* KEYS */ +#define ESCAPE_PIN D2 +#define DEVS_BT1_PIN C11 +#define DEVS_BT2_PIN C10 +#define DEVS_BT3_PIN A15 +#define DEVS_2G4_PIN C0 + +/* FLASH */ +#define SPI_DRIVER SPIDQ +#define SPI_SCK_PIN B3 +#define SPI_MOSI_PIN B5 +#define SPI_MISO_PIN B4 +#define SPI_MOSI_PAL_MODE 5 +#define EXTERNAL_FLASH_SPI_SLAVE_SELECT_PIN C12 + +/* POWER */ +#define USB_POWER_EN_PIN A14 +//#define LED_POWER_EN_PIN B13 +#define BT_CABLE_PIN B8 // High when charging +#define BT_CHARGE_PIN B9 // Low when charging, high when fully charged + +/* UART */ +#define UART_TX_PIN A9 +#define UART_TX_PAL_MODE 7 +#define UART_RX_PIN A10 +#define UART_RX_PAL_MODE 7 + +#define LED_PIN_ON_STATE 0 + +/* WIRELESS NAMES */ +#define MD_BT_NAME "NEO65 BT$" + +#define LPWR_TIMEOUT 120000 +#define WLS_KEYBOARD_REPORT_KEYS 5 diff --git a/vendor/edthu-wireless/keyboards/neo/neo65_trimode/halconf.h b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/halconf.h new file mode 100644 index 0000000..38c22d2 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/halconf.h @@ -0,0 +1,5 @@ +#pragma once + +#define PAL_USE_CALLBACKS TRUE + +#include_next diff --git a/keyboards/neo/neo65/keyboard.json b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/keyboard.json similarity index 84% rename from keyboards/neo/neo65/keyboard.json rename to vendor/edthu-wireless/keyboards/neo/neo65_trimode/keyboard.json index 43228ae..cf7324b 100644 --- a/keyboards/neo/neo65/keyboard.json +++ b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/keyboard.json @@ -1,36 +1,61 @@ { "manufacturer": "NEO", - "keyboard_name": "NEO65 Wired For Trimode ", - "maintainer": "JonyLee@HFD", - "url": "https://github.com/aliou/keebs", + "keyboard_name": "NEO65", + "maintainer": "emolitor@github", + "url": "https://www.qwertykeys.com/products/neo65-1", "processor": "WB32FQ95", "bootloader": "wb32-dfu", "usb": { - "device_version": "1.0.0", + "device_version": "0.0.1", "vid": "0xFFFE", "pid": "0x0015", - "force_nkro": true, - "suspend_wakeup_delay": 1000 + "suspend_wakeup_delay": 1000, + "no_startup_check": true }, "diode_direction": "ROW2COL", + "eeprom": { + "driver": "wear_leveling", + "wear_leveling": { + "backing_size": 8192, + "driver": "spi_flash" + } + }, + "host": { + "default": { + "nkro": true + } + }, "features": { "bootmagic": true, "command": false, "console": false, + "deferred_exec": true, "extrakey": true, "mousekey": true, "nkro": true, - "haptic": false + "macros": true + }, + "tapping": { + "term_per_key": true }, + "keycodes": [ + { "key": "KC_USB" }, + { "key": "KC_BT1" }, + { "key": "KC_BT2" }, + { "key": "KC_BT3" }, + { "key": "KC_2G4" }, + { "key": "KC_NXT" } + ], "matrix_pins": { "cols": ["C1", "C2", "C3", "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "C4", "C5", "B0", "B1", "B2"], "rows": ["C6", "C7", "C8", "C9", "B14"] }, "indicators": { - "caps_lock": "A8" + "caps_lock": "A8", + "on_state": 0 }, "layouts": { - "LAYOUT_ansi": { + "LAYOUT": { "layout": [ { "matrix": [0,0], "x": 0, "y": 0 }, { "matrix": [0,1], "x": 1, "y": 0 }, diff --git a/keyboards/neo/neo65/keymaps/via/keymap.c b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/keymaps/default/keymap.c similarity index 70% rename from keyboards/neo/neo65/keymaps/via/keymap.c rename to vendor/edthu-wireless/keyboards/neo/neo65_trimode/keymaps/default/keymap.c index faf5113..f7f37e9 100644 --- a/keyboards/neo/neo65/keymaps/via/keymap.c +++ b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/keymaps/default/keymap.c @@ -1,3 +1,4 @@ +// Copyright 2025 emolitor (@github.com/emolitor) // Copyright 2023 JoyLee (@itarze) // SPDX-License-Identifier: GPL-2.0-or-later @@ -12,7 +13,7 @@ enum layers { // clang-format off const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { - [WIN_BASE] = LAYOUT_ansi( + [WIN_BASE] = LAYOUT( KC_ESC, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSLS, KC_BSPC, KC_DEL, KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_PGUP, KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_NUHS, KC_ENT, KC_PGDN, @@ -20,15 +21,15 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, KC_RALT, MO(WIN_FN), KC_LEFT, KC_DOWN, KC_RGHT ), - [WIN_FN] = LAYOUT_ansi( - _______, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, _______, _______, EE_CLR, - _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, + [WIN_FN] = LAYOUT( + KC_USB, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, _______, _______, EE_CLR, + KC_NXT, LT(0, KC_BT1), LT(0, KC_BT2), LT(0, KC_BT3), LT(0, KC_2G4), _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______ ), - [MAC_BASE] = LAYOUT_ansi( + [MAC_BASE] = LAYOUT( _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, @@ -36,7 +37,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { _______, _______, _______, _______, _______, _______, _______, _______, _______ ), - [MAC_FN] = LAYOUT_ansi( + [MAC_FN] = LAYOUT( _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, @@ -44,37 +45,3 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { _______, _______, _______, _______, _______, _______, _______, _______, _______ ) }; -// clang-format on -uint16_t pressed_code; -uint16_t pressed_time = 0; -bool process_record_user(uint16_t keycode, keyrecord_t *record) { - // if (!process_record_keychron(keycode, record)) { - // return false; - // } - switch (keycode) { - case EE_CLR: { - pressed_code = keycode; - if (record->event.pressed) { - pressed_time = timer_read(); - } else { - pressed_time = 0; - } - return false; - } - default: - return true; - } -} - -void housekeeping_task_user(void) { - if (pressed_time && (timer_elapsed(pressed_time) >= 1500)) { - pressed_time = 0; - switch (pressed_code) { - case EE_CLR: - eeconfig_init(); - break; - default: - break; - } - } -} diff --git a/vendor/edthu-wireless/keyboards/neo/neo65_trimode/mcuconf.h b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/mcuconf.h new file mode 100644 index 0000000..63fe01f --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/mcuconf.h @@ -0,0 +1,13 @@ +// Copyright 2025 emolitor github.com/emolitor) +// Copyright (C) 2022 jonylee@hfd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include_next + +#undef WB32_SERIAL_USE_UART1 +#define WB32_SERIAL_USE_UART1 TRUE + +#undef WB32_SPI_USE_QSPI +#define WB32_SPI_USE_QSPI TRUE diff --git a/vendor/edthu-wireless/keyboards/neo/neo65_trimode/neo65_trimode.c b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/neo65_trimode.c new file mode 100644 index 0000000..25a4bd1 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/neo65_trimode.c @@ -0,0 +1,348 @@ +// Copyright 2025 emolitor (github.com/emolitor) +// Copyright 2024 Westberry Technology (ChangZhou) Corp., Ltd +// SPDX-License-Identifier: GPL-2.0-or-later*/ + +#include QMK_KEYBOARD_H +#include "wireless.h" + +typedef union { + uint32_t raw; + struct { + uint8_t flag : 1; + uint8_t devs : 3; + }; +} confinfo_t; +confinfo_t confinfo; + +uint32_t post_init_timer = 0x00; + +// Possible LED states. +enum { LED_OFF = 0, LED_ON = 1, LED_BLINK_SLOW = 2, LED_BLINK_FAST = 3 }; + +// The size of this should match the number of devices in the device index in +// westberry/wireless/module.h. Default to zero to set all LEDs to a default +// state of OFF. +static uint8_t led_blink_state[7] = {0}; + +// Implement a circular linked list of devices to support FN+TAB device +// selection +struct devs_list { + int devs; + struct devs_list *next; +}; + +struct devs_list devs[] = { + {.devs = DEVS_USB, .next = &devs[1]}, + {.devs = DEVS_BT1, .next = &devs[2]}, + {.devs = DEVS_BT2, .next = &devs[3]}, + {.devs = DEVS_BT3, .next = &devs[4]}, + {.devs = DEVS_2G4, .next = &devs[0]} +}; + +struct devs_list *current_dev = &devs[0]; // Default circular linked list to USB device + +// Hack +void md_send_devinfo(const char *name); + +// We use per-key tapping term to allow the wireless keys to have a much +// longer tapping term, therefore a longer hold, to match the default +// firmware behaviour. +uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case LT(0, KC_BT1): + case LT(0, KC_BT2): + case LT(0, KC_BT3): + case LT(0, KC_2G4): + return WIRELESS_TAPPING_TERM; + default: + return TAPPING_TERM; + } +} + +uint32_t led_blink_callback(uint32_t trigger_time, void *cb_arg) { + static const uint8_t pattern[4] = {0x00, 0xff, 0x0f, 0xaa}; + static uint8_t phase = 0; + phase = (phase + 1) % 8; + + // Default all connection indicators to off + led_blink_state[DEVS_USB] = LED_OFF; + led_blink_state[DEVS_BT1] = LED_OFF; + led_blink_state[DEVS_BT2] = LED_OFF; + led_blink_state[DEVS_BT3] = LED_OFF; + led_blink_state[DEVS_2G4] = LED_OFF; + + // Set active indicator LED mode + if (*md_getp_state() == MD_STATE_PAIRING) { + led_blink_state[confinfo.devs] = LED_BLINK_FAST; + } else if (confinfo.devs != DEVS_USB && *md_getp_state() != MD_STATE_CONNECTED) { + led_blink_state[confinfo.devs] = LED_BLINK_SLOW; + } else { + led_blink_state[confinfo.devs] = LED_ON; + } + + uint8_t bit = 1 << phase; + gpio_write_pin(ESCAPE_PIN, (pattern[led_blink_state[DEVS_USB]] & bit) != 0); + gpio_write_pin(DEVS_BT1_PIN, (pattern[led_blink_state[DEVS_BT1]] & bit) != 0); + gpio_write_pin(DEVS_BT2_PIN, (pattern[led_blink_state[DEVS_BT2]] & bit) != 0); + gpio_write_pin(DEVS_BT3_PIN, (pattern[led_blink_state[DEVS_BT3]] & bit) != 0); + gpio_write_pin(DEVS_2G4_PIN, (pattern[led_blink_state[DEVS_2G4]] & bit) != 0); + + return LED_BLINK_FAST_PERIOD_MS / 2; +} + +void keyboard_post_init_kb(void) { + confinfo.raw = eeconfig_read_kb(); + if (!confinfo.raw) { + confinfo.flag = true; + confinfo.devs = DEVS_USB; + eeconfig_update_kb(confinfo.raw); + } + + gpio_set_pin_output(ESCAPE_PIN); + gpio_set_pin_output(DEVS_BT1_PIN); + gpio_set_pin_output(DEVS_BT2_PIN); + gpio_set_pin_output(DEVS_BT3_PIN); + gpio_set_pin_output(DEVS_2G4_PIN); + + // Set GPIO as high input for battery charging state + //gpio_set_pin_input(BT_CABLE_PIN); + //gpio_set_pin_input_high(BT_CHARGE_PIN); + + // Set USB_POWER_EN_PIN state before enabling the output to avoid instability + if (confinfo.devs == DEVS_USB && gpio_read_pin(BT_CABLE_PIN)) { + gpio_write_pin_low(USB_POWER_EN_PIN); + } else { + gpio_write_pin_high(USB_POWER_EN_PIN); + } + gpio_set_pin_output(USB_POWER_EN_PIN); + + wireless_init(); + md_send_devinfo(MD_BT_NAME); + wait_ms(10); + wireless_devs_change(!confinfo.devs, confinfo.devs, false); + post_init_timer = timer_read32(); + + defer_exec(LED_BLINK_FAST_PERIOD_MS / 2, led_blink_callback, NULL); + + keyboard_post_init_user(); +} + +void usb_power_connect(void) { + gpio_write_pin_low(USB_POWER_EN_PIN); +} + +void usb_power_disconnect(void) { + gpio_write_pin_high(USB_POWER_EN_PIN); +} + +void suspend_wakeup_init_kb(void) { + wireless_devs_change(wireless_get_current_devs(), wireless_get_current_devs(), false); + suspend_wakeup_init_user(); +} + +bool lpwr_is_allow_timeout_hook(void) { + if (wireless_get_current_devs() == DEVS_USB) { + return false; + } + + return true; +} + +void wireless_post_task(void) { + if (post_init_timer && timer_elapsed32(post_init_timer) >= 100) { + md_send_devctrl(MD_SND_CMD_DEVCTRL_FW_VERSION); // get the module fw version. + md_send_devctrl(MD_SND_CMD_DEVCTRL_SLEEP_BT_EN); // timeout 30min to sleep in bt mode, enable + md_send_devctrl(MD_SND_CMD_DEVCTRL_SLEEP_2G4_EN); // timeout 30min to sleep in 2.4g mode, enable + wireless_devs_change(!confinfo.devs, confinfo.devs, false); + post_init_timer = 0x00; + } +} + +void md_devs_change(uint8_t devs, bool reset) { + switch (devs) { + case DEVS_USB: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_USB); + } break; + case DEVS_2G4: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_2G4); + if (reset) { + md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); + } + } break; + case DEVS_BT1: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_BT1); + if (reset) { + md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); + } + } break; + case DEVS_BT2: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_BT2); + if (reset) { + md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); + } + } break; + case DEVS_BT3: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_BT3); + if (reset) { + md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); + } + } break; + default: + break; + } +} + +bool process_record_kb(uint16_t keycode, keyrecord_t *record) { + if (process_record_user(keycode, record) != true) { + return false; + } + + switch (keycode) { + case KC_USB: { + wireless_devs_change(wireless_get_current_devs(), DEVS_USB, false); + return false; + } + case KC_NXT: { + if (record->event.pressed) { + current_dev = current_dev->next; + wireless_devs_change(wireless_get_current_devs(), current_dev->devs, false); + return false; + } + } + case LT(0, KC_BT1): { + if (record->tap.count && record->event.pressed) { + wireless_devs_change(wireless_get_current_devs(), DEVS_BT1, false); + } else if (record->event.pressed && *md_getp_state() != MD_STATE_PAIRING) { + wireless_devs_change(wireless_get_current_devs(), DEVS_BT1, true); + } + return false; + } + case LT(0, KC_BT2): { + if (record->tap.count && record->event.pressed) { + wireless_devs_change(wireless_get_current_devs(), DEVS_BT2, false); + } else if (record->event.pressed && *md_getp_state() != MD_STATE_PAIRING) { + wireless_devs_change(wireless_get_current_devs(), DEVS_BT2, true); + } + return false; + } + case LT(0, KC_BT3): { + if (record->tap.count && record->event.pressed) { + wireless_devs_change(wireless_get_current_devs(), DEVS_BT3, false); + } else if (record->event.pressed && *md_getp_state() != MD_STATE_PAIRING) { + wireless_devs_change(wireless_get_current_devs(), DEVS_BT3, true); + } + return false; + } + case LT(0, KC_2G4): { + if (record->tap.count && record->event.pressed) { + wireless_devs_change(wireless_get_current_devs(), DEVS_2G4, false); + } else if (record->event.pressed && *md_getp_state() != MD_STATE_PAIRING) { + wireless_devs_change(wireless_get_current_devs(), DEVS_2G4, true); + } + return false; + } + default: + return true; + } +} + +void wireless_devs_change_kb(uint8_t old_devs, uint8_t new_devs, bool reset) { + if (confinfo.devs != wireless_get_current_devs()) { + confinfo.devs = wireless_get_current_devs(); + eeconfig_update_kb(confinfo.raw); + } +} + +// Force MCU reset on unhandled_exception +void _unhandled_exception(void) { + mcu_reset(); +} + +// Exprimental change to fix duplicate and hung key presses on wireless +void wireless_send_nkro(report_nkro_t *report) { + static report_keyboard_t temp_report_keyboard = {0}; + uint8_t wls_report_nkro[MD_SND_CMD_NKRO_LEN] = {0}; + +#ifdef NKRO_ENABLE + if (report != NULL) { + report_nkro_t temp_report_nkro = *report; + uint8_t key_count = 0; + + temp_report_keyboard.mods = temp_report_nkro.mods; + for (uint8_t i = 0; i < NKRO_REPORT_BITS; i++) { + key_count += __builtin_popcount(temp_report_nkro.bits[i]); + } + + /* + * Use NKRO for sending when more than 6 keys are pressed + * to solve the issue of the lack of a protocol flag in wireless mode. + */ + + for (uint8_t i = 0; i < key_count; i++) { + uint8_t usageid; + uint8_t idx, n = 0; + + for (n = 0; n < NKRO_REPORT_BITS && !temp_report_nkro.bits[n]; n++) { + } + usageid = (n << 3) | biton(temp_report_nkro.bits[n]); + del_key_bit(&temp_report_nkro, usageid); + + for (idx = 0; idx < WLS_KEYBOARD_REPORT_KEYS; idx++) { + if (temp_report_keyboard.keys[idx] == usageid) { + goto next; + } + } + + for (idx = 0; idx < WLS_KEYBOARD_REPORT_KEYS; idx++) { + if (temp_report_keyboard.keys[idx] == 0x00) { + temp_report_keyboard.keys[idx] = usageid; + break; + } + } + next: + if (idx == WLS_KEYBOARD_REPORT_KEYS && (usageid < (MD_SND_CMD_NKRO_LEN * 8))) { + wls_report_nkro[usageid / 8] |= 0x01 << (usageid % 8); + } + } + + temp_report_nkro = *report; + + // find key up and del it. + uint8_t nkro_keys = key_count; + for (uint8_t i = 0; i < WLS_KEYBOARD_REPORT_KEYS; i++) { + report_nkro_t found_report_nkro; + uint8_t usageid = 0x00; + uint8_t n; + + found_report_nkro = temp_report_nkro; + + for (uint8_t c = 0; c < nkro_keys; c++) { + for (n = 0; n < NKRO_REPORT_BITS && !found_report_nkro.bits[n]; n++) { + } + usageid = (n << 3) | biton(found_report_nkro.bits[n]); + del_key_bit(&found_report_nkro, usageid); + if (usageid == temp_report_keyboard.keys[i]) { + del_key_bit(&temp_report_nkro, usageid); + nkro_keys--; + break; + } + } + + if (usageid != temp_report_keyboard.keys[i]) { + temp_report_keyboard.keys[i] = 0x00; + } + } + + } else { + memset(&temp_report_keyboard, 0, sizeof(temp_report_keyboard)); + } +#endif + void wireless_task(void); + bool smsg_is_busy(void); + while (smsg_is_busy()) { + wireless_task(); + } + extern host_driver_t wireless_driver; + wireless_driver.send_keyboard(&temp_report_keyboard); + md_send_nkro(wls_report_nkro); +} diff --git a/vendor/edthu-wireless/keyboards/neo/neo65_trimode/readme.md b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/readme.md new file mode 100644 index 0000000..8100893 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/readme.md @@ -0,0 +1,43 @@ +# Neo65 Tri-mode +The Neo65 Tri-mode is based on a Westberry Tech WB32FQ95 MCU in a LQFP64 +package and can be programmed with wb32-dfu. The keyboard uses a WCH CH582F +chip running a proprietary firmware to provide wireless connectivity. This is +a community port which was derived from a partial source originally released +by the manufacturer and reverse engineered by probing the hardware directly. + + +## Flashing a new firmware +Hold ESCAPE [0,0] to enter bootloader mode while inserting the USB cable into +the keyboard. Then run the following to flash the firmware. +```shell +qmk flash -kb neo/neo65_trimode -km default +``` + + +## Quirks +The Neo65 uses a slightly different way to initialize the bluetooth device +names. As the wireless stack is initialized in `keyboard_post_init_kb` a call +is made to `md_send_devinfo` where the bluetooth name is passed but with the +`$` character as the last character. The `$` is replaced with the BT device +number when pairing. This seems to be a feature unique to this version of the +CH582F firmware. This also means during pairing we do not 'CLEAN' the device +and we do not upddate the device name. We simply send PAIR to `md_send_devctrl`. + + +## How are the LEDs wired? +* PD2 is wired to the ESC LED +* PA8 is wired to the CAPSLOCK LED +* PC11 is wired to the Q LED +* PC10 is wired to the W LED +* PA15 is wired to the E LED +* PC0 is wired to the R LED + + +## Reference Material +The following shouldn't be upstreamed but I've included documentation relevant +for porting in the repo to have everything in a single place. +* [WB32FQ95 Data Sheet](../../../em-documentation/EN_DS1104041_WB32FQ95xC_V01.pdf) +* [WB32FQ95 Reference Manual](../../../em-documentation/EN_RM2905025_WB32FQ95xx_V01.pdf) +* [WCH CH582F Data Sheet](../../../em-documentation/CH583DS1.PDF) +* [Puya P25D80SH Data Sheet](../../../em-documentation/PUYA-P25Q80H-SSH-IT_C194872.pdf) +* [TP4056 Data Sheet](../../../em-documentation/TP4056.pdf) diff --git a/vendor/edthu-wireless/keyboards/neo/neo65_trimode/rules.mk b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/rules.mk new file mode 100644 index 0000000..a38990f --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/neo65_trimode/rules.mk @@ -0,0 +1 @@ +include keyboards/neo/wireless/wireless.mk diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/lowpower.c b/vendor/edthu-wireless/keyboards/neo/wireless/lowpower.c new file mode 100644 index 0000000..ab530f9 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/lowpower.c @@ -0,0 +1,312 @@ +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "quantum.h" +#include "wireless.h" +#include "usb_main.h" + +#ifndef LPWR_TIMEOUT +# define LPWR_TIMEOUT 300000 // 5min +#endif + +#ifndef LPWR_PRESLEEP_DELAY +# define LPWR_PRESLEEP_DELAY 200 +#endif + +#ifndef LPWR_STOP_DELAY +# define LPWR_STOP_DELAY 200 +#endif + +#ifndef LPWR_WAKEUP_DELAY +# define LPWR_WAKEUP_DELAY 200 +#endif + +static lpwr_state_t lpwr_state = LPWR_NORMAL; +static lpwr_mode_t lpwr_mode = LPWR_MODE_TIMEOUT; +static uint32_t lpwr_timeout_value = LPWR_TIMEOUT; +static uint32_t lpwr_timestamp = 0x00; +static lpwr_wakeupcd_t lpwr_wakeupcd = LPWR_WAKEUP_NONE; +static bool manual_timeout = false; + +static bool rgb_enable_bak = false; + +void last_matrix_activity_trigger(void); + +void lpwr_clock_enable(void); +void lpwr_enter_stop(void); +void lpwr_exti_init(void); +void mcu_stop_mode(void); + +extern void matrix_init_pins(void); + +lpwr_state_t lpwr_get_state(void) { + return lpwr_state; +} + +void lpwr_set_state(lpwr_state_t state) { + lpwr_state = state; +} + +lpwr_mode_t lpwr_get_mode(void) { + return lpwr_mode; +} + +void lpwr_set_mode(lpwr_mode_t mode) { + lpwr_mode = mode; +} + +void lpwr_set_timeout_value(uint32_t timeout) { + lpwr_timeout_value = timeout; +} + +uint32_t lpwr_timeout_value_read(void) { + return lpwr_timeout_value; +} + +void lpwr_update_timestamp(void) { + lpwr_timestamp = sync_timer_read32(); +} + +uint32_t lpwr_timestamp_read(void) { + return lpwr_timestamp; +} + +void lpwr_set_sleep_wakeupcd(lpwr_wakeupcd_t wakeupcd) { + lpwr_wakeupcd = wakeupcd; +} + +lpwr_wakeupcd_t lpwr_get_sleep_wakeupcd(void) { + return lpwr_wakeupcd; +} + +void lpwr_clock_enable(void) __attribute__((weak)); +void lpwr_clock_enable(void) {} + +void lpwr_exti_init(void) __attribute__((weak)); +void lpwr_exti_init(void) {} + +void mcu_stop_mode(void) __attribute__((weak)); +void mcu_stop_mode(void) {} + +void lpwr_enter_stop(void) { + chSysLock(); + lpwr_exti_init(); + chSysUnlock(); + + chSysDisable(); + mcu_stop_mode(); + lpwr_clock_enable(); + matrix_init_pins(); + chSysEnable(); +} + +void lpwr_set_timeout_manual(bool enable) { + manual_timeout = enable; +} + +bool lpwr_get_timeout_manual(void) { + return manual_timeout; +} + +// 2.4g mode, host state +void md_receive_host_cb(bool resume) { + + if (resume) { + if (lpwr_get_state() != LPWR_NORMAL) { + lpwr_update_timestamp(); + lpwr_set_state(LPWR_WAKEUP); + } + } else { + if (lpwr_get_state() == LPWR_NORMAL) { + manual_timeout = true; + } + } +} + +bool lpwr_is_allow_timeout_hook(void) __attribute__((weak)); +bool lpwr_is_allow_timeout_hook(void) { + return true; +} + +bool lpwr_is_allow_timeout(void) __attribute__((weak)); +bool lpwr_is_allow_timeout(void) { + uint32_t timeout = lpwr_timeout_value_read(); + + if (lpwr_is_allow_timeout_hook() != true) { + manual_timeout = false; + return false; + } + + //if ((wireless_get_current_devs() == DEVS_USB) && (USB_DRIVER.state == USB_ACTIVE)) { + if (wireless_get_current_devs() == DEVS_USB) { + manual_timeout = false; + return false; + } + + if (manual_timeout || (timeout && (last_input_activity_elapsed() >= timeout))) { + manual_timeout = false; + return true; + } + + return false; +} + +bool lpwr_is_allow_presleep_hook(void) __attribute__((weak)); +bool lpwr_is_allow_presleep_hook(void) { + return true; +} + +bool lpwr_is_allow_presleep(void) __attribute__((weak)); +bool lpwr_is_allow_presleep(void) { + uint32_t delay = LPWR_PRESLEEP_DELAY; + + if (lpwr_is_allow_presleep_hook() != true) { + return false; + } + + if (!delay || (sync_timer_elapsed32(lpwr_timestamp_read()) >= delay)) { + return true; + } + + return false; +} + +bool lpwr_is_allow_stop_hook(void) __attribute__((weak)); +bool lpwr_is_allow_stop_hook(void) { + return true; +} + +bool lpwr_is_allow_stop(void) __attribute__((weak)); +bool lpwr_is_allow_stop(void) { + uint32_t delay = LPWR_STOP_DELAY; + + if (lpwr_is_allow_stop_hook() != true) { + return false; + } + + if (!delay || (sync_timer_elapsed32(lpwr_timestamp_read()) >= delay)) { + return true; + } + + return false; +} + +bool lpwr_is_allow_wakeup_hook(void) __attribute__((weak)); +bool lpwr_is_allow_wakeup_hook(void) { + return true; +} + +bool lpwr_is_allow_wakeup(void) __attribute__((weak)); +bool lpwr_is_allow_wakeup(void) { + uint32_t delay = LPWR_WAKEUP_DELAY; + + if (lpwr_is_allow_wakeup_hook() != true) { + return false; + } + + if (!delay || (sync_timer_elapsed32(lpwr_timestamp_read()) >= delay)) { + return true; + } + + return false; +} + +void lpwr_presleep_hook(void) __attribute__((weak)); +void lpwr_presleep_hook(void) {} + +void lpwr_presleep_cb(void) __attribute__((weak)); +void lpwr_presleep_cb(void) { + +#if defined(RGB_MATRIX_ENABLE) + rgb_enable_bak = rgb_matrix_is_enabled(); + rgb_matrix_disable_noeeprom(); +#elif defined(RGBLIGHT_ENABLE) + rgb_enable_bak = rgblight_is_enabled(); + rgblight_disable_noeeprom(); +#else + rgb_enable_bak = false; +#endif + suspend_power_down(); + lpwr_presleep_hook(); +} + +void lpwr_stop_hook_pre(void) __attribute__((weak)); +void lpwr_stop_hook_pre(void) {} + +void lpwr_stop_hook_post(void) __attribute__((weak)); +void lpwr_stop_hook_post(void) {} + +void lpwr_stop_cb(void) __attribute__((weak)); +void lpwr_stop_cb(void) { + + lpwr_set_sleep_wakeupcd(LPWR_WAKEUP_NONE); + + lpwr_stop_hook_pre(); + lpwr_enter_stop(); + + switch (lpwr_get_sleep_wakeupcd()) { + case LPWR_WAKEUP_UART: { + lpwr_set_state(LPWR_STOP); + } break; + default: { + lpwr_set_state(LPWR_WAKEUP); + } break; + } + + lpwr_stop_hook_post(); +} + +void lpwr_wakeup_hook(void) __attribute__((weak)); +void lpwr_wakeup_hook(void) {} + +void lpwr_wakeup_cb(void) __attribute__((weak)); +void lpwr_wakeup_cb(void) { + + if (rgb_enable_bak) { +#if defined(RGB_MATRIX_ENABLE) + rgb_matrix_enable_noeeprom(); +#elif defined(RGBLIGHT_ENABLE) + rgblight_enable_noeeprom(); +#endif + } + + suspend_wakeup_init(); + lpwr_wakeup_hook(); + + last_matrix_activity_trigger(); +} + +void lpwr_task(void) __attribute__((weak)); +void lpwr_task(void) { + + switch (lpwr_get_state()) { + case LPWR_NORMAL: { + if (lpwr_is_allow_timeout()) { + lpwr_update_timestamp(); + lpwr_set_state(LPWR_PRESLEEP); + } + } break; + case LPWR_PRESLEEP: { + if (lpwr_is_allow_presleep()) { + lpwr_presleep_cb(); + lpwr_update_timestamp(); + lpwr_set_state(LPWR_STOP); + } + } break; + case LPWR_STOP: { + if (lpwr_is_allow_stop()) { + lpwr_update_timestamp(); + lpwr_stop_cb(); + } + } break; + case LPWR_WAKEUP: { + if (lpwr_is_allow_wakeup()) { + lpwr_wakeup_cb(); + lpwr_update_timestamp(); + lpwr_set_state(LPWR_NORMAL); + } + } break; + default: + break; + } +} diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/lowpower.h b/vendor/edthu-wireless/keyboards/neo/wireless/lowpower.h new file mode 100644 index 0000000..e7d60a4 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/lowpower.h @@ -0,0 +1,39 @@ +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +typedef enum { + LPWR_NORMAL = 0, + LPWR_PRESLEEP, + LPWR_STOP, + LPWR_WAKEUP, +} lpwr_state_t; + +typedef enum { + LPWR_WAKEUP_NONE = 0, + LPWR_WAKEUP_MATRIX, + LPWR_WAKEUP_UART, + LPWR_WAKEUP_CABLE, + LPWR_WAKEUP_USB, + LPWR_WAKEUP_ONEKEY, + LPWR_WAKEUP_ENCODER, + LPWR_WAKEUP_SWITCH, +} lpwr_wakeupcd_t; + +typedef enum { + LPWR_MODE_TIMEOUT = 0, +} lpwr_mode_t; + +lpwr_state_t lpwr_get_state(void); +lpwr_mode_t lpwr_get_mode(void); +uint32_t lpwr_timestamp_read(void); +uint32_t lpwr_timeout_value_read(void); +void lpwr_set_sleep_wakeupcd(lpwr_wakeupcd_t wakeupcd); +lpwr_wakeupcd_t lpwr_get_sleep_wakeupcd(void); +void lpwr_update_timestamp(void); +void lpwr_set_timeout_manual(bool enable); +bool lpwr_get_timeout_manual(void); +void lpwr_set_state(lpwr_state_t state); +void lpwr_set_mode(lpwr_mode_t mode); +void lpwr_task(void); diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/lpwr_wb32.c b/vendor/edthu-wireless/keyboards/neo/wireless/lpwr_wb32.c new file mode 100644 index 0000000..65bc298 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/lpwr_wb32.c @@ -0,0 +1,209 @@ +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "quantum.h" +#include "wireless.h" +#include "util.h" + +#ifndef LPWR_UART_WAKEUP_DISABLE +# include "uart.h" +#endif + +static ioline_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS; +static ioline_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS; + +#if PAL_USE_CALLBACKS != TRUE +# error PAL_USE_CALLBACKS must be set to TRUE! +#endif + +#if !((DIODE_DIRECTION == ROW2COL) || (DIODE_DIRECTION == COL2ROW)) +# error DIODE_DIRECTION must be one of COL2ROW or ROW2COL! +#endif + +// clang-format off +static const uint32_t pre_lp_code[] = {553863175u, 554459777u, 1208378049u, 4026624001u, 688390415u, 554227969u, 3204472833u, 1198571264u, 1073807360u, 1073808388u}; +#define PRE_LP() ((void (*)(void))((unsigned int)(pre_lp_code) | 0x01))() + +static const uint32_t post_lp_code[] = {553863177u, 554459777u, 1208509121u, 51443856u, 4026550535u, 1745485839u, 3489677954u, 536895496u, 673389632u, 1198578684u, 1073807360u, 536866816u, 1073808388u}; +#define POST_LP() ((void (*)(void))((unsigned int)(post_lp_code) | 0x01))() +// clang-format on + +extern void __early_init(void); +extern void matrix_init_pins(void); + +void palcallback_cb(uint8_t line) __attribute__((weak)); +void palcallback_cb(uint8_t line) {} + +void palcallback(void *arg) { + uint8_t line = (uint32_t)arg & 0xFF; + + switch (line) { +#ifndef LPWR_UART_WAKEUP_DISABLE + case PAL_PAD(UART_RX_PIN): { + lpwr_set_sleep_wakeupcd(LPWR_WAKEUP_UART); + } break; +#endif + default: { + lpwr_set_sleep_wakeupcd(LPWR_WAKEUP_MATRIX); + } break; + } + + palcallback_cb(line); + + irqDeinit(); + EXTI->PR = 0xFFFFFFFF; +} + +void pal_events_init(void) { + + for (uint8_t i = 0; i < 16; i++) { + _pal_events[i].cb = palcallback; + _pal_events[i].arg = (void *)(uint32_t)i; + } +} + +void lpwr_exti_init_hook(void) __attribute__((weak)); +void lpwr_exti_init_hook(void) {} + +void lpwr_exti_init(void) { + + pal_events_init(); + +#if DIODE_DIRECTION == ROW2COL + for (uint8_t i = 0; i < ARRAY_SIZE(col_pins); i++) { + if (col_pins[i] != NO_PIN) { + gpio_set_pin_output_open_drain(col_pins[i]); + gpio_write_pin_low(col_pins[i]); + } + } + + for (uint8_t i = 0; i < ARRAY_SIZE(row_pins); i++) { + if (row_pins[i] != NO_PIN) { + gpio_set_pin_input_high(row_pins[i]); + waitInputPinDelay(); + palEnableLineEvent(row_pins[i], PAL_EVENT_MODE_BOTH_EDGES); + } + } +#elif DIODE_DIRECTION == COL2ROW + for (uint8_t i = 0; i < ARRAY_SIZE(row_pins); i++) { + if (row_pins[i] != NO_PIN) { + gpio_set_pin_output_open_drain(row_pins[i]); + gpio_write_pin_low(row_pins[i]); + } + } + + for (uint8_t i = 0; i < ARRAY_SIZE(col_pins); i++) { + if (col_pins[i] != NO_PIN) { + gpio_set_pin_input_high(col_pins[i]); + waitInputPinDelay(); + palEnableLineEvent(col_pins[i], PAL_EVENT_MODE_BOTH_EDGES); + } + } +#endif + +#ifndef LPWR_UART_WAKEUP_DISABLE + gpio_set_pin_input(UART_RX_PIN); + waitInputPinDelay(); + palEnableLineEvent(UART_RX_PIN, PAL_EVENT_MODE_BOTH_EDGES); +#endif + + lpwr_exti_init_hook(); + + /* IRQ subsystem initialization.*/ + irqInit(); +} + +void lpwr_clock_enable_user(void) __attribute__((weak)); +void lpwr_clock_enable_user(void) {} + +void lpwr_clock_enable(void) { + + __early_init(); + + rccEnableEXTI(); + +#if WB32_SERIAL_USE_UART1 + rccEnableUART1(); +#endif +#if WB32_SERIAL_USE_UART2 + rccEnableUART2(); +#endif +#if WB32_SERIAL_USE_UART3 + rccEnableUART3(); +#endif +#if WB32_SPI_USE_QSPI + rccEnableQSPI(); +#endif +#if WB32_SPI_USE_SPIM2 + rccEnableSPIM2(); +#endif +#if WB32_I2C_USE_I2C1 + rccEnableI2C1(); +#endif +#if WB32_I2C_USE_I2C2 + rccEnableI2C2(); +#endif +#if WB32_GPT_USE_TIM1 || WB32_ICU_USE_TIM1 || WB32_PWM_USE_TIM1 + rccEnableTIM1(); +#endif +#if WB32_ST_USE_TIM2 || WB32_GPT_USE_TIM2 || WB32_ICU_USE_TIM2 || WB32_PWM_USE_TIM2 + rccEnableTIM2(); +#endif +#if WB32_ST_USE_TIM3 || WB32_GPT_USE_TIM3 || WB32_ICU_USE_TIM3 || WB32_PWM_USE_TIM3 + rccEnableTIM3(); +#endif +#if WB32_ST_USE_TIM4 || WB32_GPT_USE_TIM4 || WB32_ICU_USE_TIM4 || WB32_PWM_USE_TIM4 + rccEnableTIM4(); +#endif + +#ifndef LPWR_UART_WAKEUP_DISABLE + palSetLineMode(UART_RX_PIN, PAL_MODE_ALTERNATE(UART_RX_PAL_MODE) | PAL_OUTPUT_TYPE_PUSHPULL | PAL_OUTPUT_SPEED_HIGHEST); +#endif + + lpwr_clock_enable_user(); +} + +void wb32_stop_mode(void) { + + SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; + + /* Prevent the chip from being unable to enter stop mode due to pending interrupts */ +#if 1 + EXTI->PR = 0x7FFFF; + for (uint8_t i = 0; i < 8; i++) { + for (uint8_t j = 0; j < 32; j++) { + if (NVIC->ISPR[i] & (0x01UL < j)) { + NVIC->ICPR[i] = (0x01UL < j); + } + } + } + SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk; // Clear Systick IRQ Pending +#endif + + /* Clear all bits except DBP and FCLKSD bit */ + PWR->CR0 &= 0x09U; + + // STOP LP4 MODE S32KON + PWR->CR0 |= 0x3B004U; + PWR->CFGR = 0x3B3; + + PRE_LP(); + + /* Set SLEEPDEEP bit of Cortex System Control Register */ + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + + /* Request Wait For Interrupt */ + __WFI(); + + POST_LP(); + + /* Clear SLEEPDEEP bit of Cortex System Control Register */ + SCB->SCR &= (~SCB_SCR_SLEEPDEEP_Msk); + + SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; +} + +void mcu_stop_mode(void) { + + wb32_stop_mode(); +} diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/md_raw.c b/vendor/edthu-wireless/keyboards/neo/wireless/md_raw.c new file mode 100644 index 0000000..eebfc94 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/md_raw.c @@ -0,0 +1,30 @@ +// Copyright 2024 QMK +// SPDX-License-Identifier: GPL-2.0-or-later + +#if RAW_ENABLE + +# include "quantum.h" +# include "wireless.h" +# include "usb_endpoints.h" +# include "usb_main.h" + +void replaced_hid_send(uint8_t *data, uint8_t length) { + + if (length != RAW_EPSIZE) { + return; + } + + if (get_transport() == TRANSPORT_USB) { + send_report(USB_ENDPOINT_IN_RAW, data, length); + } else { + md_send_raw(data, length); + } +} + +void md_receive_raw_cb(uint8_t *data, uint8_t length) { + void raw_hid_receive(uint8_t * data, uint8_t length); + + raw_hid_receive(data, length); +} + +#endif diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/md_raw.h b/vendor/edthu-wireless/keyboards/neo/wireless/md_raw.h new file mode 100644 index 0000000..9ec762b --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/md_raw.h @@ -0,0 +1,10 @@ +// Copyright 2024 QMK +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#define RENAME_WITH_LINE(A, B) COMBINE(A, B) +#define COMBINE(A, B) A##B +#define raw_hid_send(a, b) RENAME_WITH_LINE(_temp_rhs_, __LINE__)(a, b) +#define _temp_rhs_29 replaced_hid_send // raw_hid.h +#define _temp_rhs_464 replaced_hid_send // via.c diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/module.c b/vendor/edthu-wireless/keyboards/neo/wireless/module.c new file mode 100644 index 0000000..78f98ff --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/module.c @@ -0,0 +1,530 @@ +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "quantum.h" +#include "module.h" +#include "smsg.h" +#include "uart.h" + +#ifndef MD_BAUD_RATE +# define MD_BAUD_RATE 115200 +#endif + +#ifndef MD_SNED_PKT_TIMEOUT +# define MD_SNED_PKT_TIMEOUT 10 +#endif + +#ifndef MD_SEND_PKT_RETRY +# define MD_SEND_PKT_RETRY 40 +#endif + +#ifndef MD_SEND_PKT_PAYLOAD_MAX +# define MD_SEND_PKT_PAYLOAD_MAX ((MD_RAW_SIZE) + 4) +#endif + +#ifndef MD_BT1_NAME +# define MD_BT1_NAME PRODUCT " BT1" +#endif + +#ifndef MD_BT2_NAME +# define MD_BT2_NAME PRODUCT " BT2" +#endif + +#ifndef MD_BT3_NAME +# define MD_BT3_NAME PRODUCT " BT3" +#endif + +#ifndef MD_BT4_NAME +# define MD_BT4_NAME PRODUCT " BT4" +#endif + +#ifndef MD_BT5_NAME +# define MD_BT5_NAME PRODUCT " BT5" +#endif + +#ifndef MD_DONGLE_MANUFACTURER +# define MD_DONGLE_MANUFACTURER MANUFACTURER +#endif + +#ifndef MD_DONGLE_PRODUCT +# define MD_DONGLE_PRODUCT PRODUCT " Dongle" +#endif + +#ifndef MD_RAW_SIZE +# define MD_RAW_SIZE 32 +#endif + +#define USBCONCAT(a, b) a##b +#define USBSTR(s) USBCONCAT(L, s) + +typedef struct +{ + uint8_t state; + uint8_t indicator; + uint8_t version; + uint8_t bat; +} md_info_t; + +static uint8_t md_pkt_payload[MD_SEND_PKT_PAYLOAD_MAX] = {0}; +static uint8_t md_rev_payload[MD_SEND_PKT_PAYLOAD_MAX] = {0}; +static uint8_t md_raw_payload[MD_RAW_SIZE] = {0}; + +static md_info_t md_info = { + .bat = 100, + .indicator = 0, + .version = 0, + .state = MD_STATE_NONE, +}; + +static void md_send_ack(void) { + + uint8_t sdata[0x03] = {0x61, 0x0D, 0x0A}; + uart_transmit(sdata, sizeof(sdata)); +} + +static bool md_check_sum(const uint8_t *data, uint32_t length) { + uint8_t sum = 0; + + for (uint32_t i = 0; i < (length - 1); i++) { + sum += data[i]; + } + + return sum == data[length - 1]; +} + +static void md_calc_check_sum(uint8_t *data, uint32_t length) { + uint8_t sum = 0; + + for (uint32_t i = 0; i < length; i++) { + sum += data[i]; + } + + data[length] = sum; +} + +bool md_receive_process_user(uint8_t *pdata, uint8_t len) __attribute__((weak)); +bool md_receive_process_user(uint8_t *pdata, uint8_t len) { + return true; +} + +bool md_receive_process_kb(uint8_t *pdata, uint8_t len) __attribute__((weak)); +bool md_receive_process_kb(uint8_t *pdata, uint8_t len) { + return md_receive_process_user(pdata, len); +} + +void md_receive_raw_cb(uint8_t *pdata, uint8_t len) __attribute__((weak)); +void md_receive_raw_cb(uint8_t *pdata, uint8_t len) {} + +void md_receive_host_cb(bool resume) __attribute__((weak)); +void md_receive_host_cb(bool resume) {} + +static void md_receive_msg_task(void) { + static uint32_t data_count = 0x00; + static uint8_t data_remain = 0x00; + + while (uart_available()) { + uint8_t data = uart_read(); + + switch (data_count) { + case 0: { // cmd + switch (data) { + case MD_REV_CMD_INDICATOR: + case MD_REV_CMD_DEVCTRL: + case MD_REV_CMD_BATVOL: + case MD_REV_CMD_MD_FW_VERSION: + case MD_REV_CMD_HOST_STATE: + case 0x61: { + md_rev_payload[data_count++] = data; + data_remain = 2; + } break; + case MD_REV_CMD_RAW: { + md_rev_payload[data_count++] = data; + data_remain = 33; + } break; + default: { + data_count = 0; + } break; + } + continue; + } break; + case 1: { + md_rev_payload[data_count++] = data; + data_remain--; + continue; + } break; + case 2: { + // ACK + if ((md_rev_payload[0] == 0x61) && (md_rev_payload[1] == 0x0D) && (data == 0x0A)) { + if (smsg_get_state() == smsg_state_busy) { + smsg_set_state(smsg_state_replied); + } + data_count = 0; + return; + } + } + default: { + md_rev_payload[data_count++] = data; + data_remain--; + + if (data_remain) { + continue; + } + } break; + } + + if (md_check_sum(md_rev_payload, data_count)) { + md_send_ack(); + + if (md_receive_process_kb(md_rev_payload, data_count) != true) { + return; + } + + switch (md_rev_payload[0]) { + case MD_REV_CMD_RAW: { + uint8_t *pdata; + pdata = &md_rev_payload[1]; + + memcpy(md_raw_payload, pdata, 32); + md_receive_raw_cb(md_raw_payload, 32); + + } break; + case MD_REV_CMD_INDICATOR: { + md_info.indicator = md_rev_payload[1]; + } break; + case MD_REV_CMD_DEVCTRL: { + switch (md_rev_payload[1]) { + case MD_REV_CMD_DEVCTRL_PAIRING: { + md_info.state = MD_STATE_PAIRING; + } break; + case MD_REV_CMD_DEVCTRL_CONNECTED: { + md_info.state = MD_STATE_CONNECTED; + } break; + case MD_REV_CMD_DEVCTRL_DISCONNECTED: { + md_info.state = MD_STATE_DISCONNECTED; + } break; + case MD_REV_CMD_DEVCTRL_REJECT: { + md_info.state = MD_STATE_REJECT; + } break; + default: + break; + } + } break; + case MD_REV_CMD_BATVOL: { + md_info.bat = md_rev_payload[1]; + } break; + case MD_REV_CMD_MD_FW_VERSION: { + md_info.version = md_rev_payload[1]; + } break; + case MD_REV_CMD_HOST_STATE: { + md_receive_host_cb(md_rev_payload[1] == MD_REV_CMD_HOST_STATE_RESUME); + } break; + default: + break; + } + } + data_count = 0; + } +} + +static void md_send_pkt_task(void) { + static uint32_t smsg_timer = 0x00; + static uint8_t smsg_retry = 0; + + switch (smsg_get_state()) { + case smsg_state_busy: { + if (sync_timer_elapsed32(smsg_timer) > (MD_SNED_PKT_TIMEOUT)) { + smsg_retry = 0; + smsg_set_state(smsg_state_retry); + } + } break; + case smsg_state_retry: { + if (++smsg_retry > MD_SEND_PKT_RETRY) { + smsg_retry = 0; + smsg_pop(); + } + smsg_set_state(smsg_state_free); + } break; + case smsg_state_replied: { + smsg_pop(); + smsg_set_state(smsg_state_free); + } // break; + case smsg_state_free: { + uint32_t size = smsg_peek(md_pkt_payload); + if (size) { + if (md_send_pkt(md_pkt_payload, size)) { + smsg_timer = sync_timer_read32(); + smsg_set_state(smsg_state_busy); + } else { + smsg_set_state(smsg_state_replied); + } + } + } break; + default: + break; + } +} + +void md_init(void) { + + uart_init(MD_BAUD_RATE); + smsg_init(); + + memset(md_pkt_payload, 0, sizeof(md_pkt_payload)); +} + +void md_main_task(void) { + + md_send_pkt_task(); + md_receive_msg_task(); +} + +uint8_t *md_getp_state(void) { + + return &md_info.state; +} + +uint8_t *md_getp_bat(void) { + + return &md_info.bat; +} + +uint8_t *md_getp_indicator(void) { + + return &md_info.indicator; +} + +uint8_t md_get_version(void) { + + return md_info.version; +} + +bool md_send_pkt(uint8_t *data, uint32_t len) { + bool retval = true; + + if (!data || !len) { + return false; + } + + // switch (*data) { + // case MD_SND_CMD_RAW: { + // retval = false; + // } break; + // default: + // break; + // } + + // send + uart_transmit(data, len); + + return retval; +} + +void md_send_kb(uint8_t *data) { + uint8_t sdata[MD_SND_CMD_KB_LEN + 2] = {0x00}; + + sdata[0] = MD_SND_CMD_SEND_KB; + memcpy(&sdata[1], data, sizeof(sdata) - 2); + md_calc_check_sum(sdata, sizeof(sdata) - 1); + smsg_push(sdata, sizeof(sdata)); +} + +void md_send_nkro(uint8_t *data) { + uint8_t sdata[MD_SND_CMD_NKRO_LEN + 2] = {0x00}; + + sdata[0] = MD_SND_CMD_SEND_NKRO; + memcpy(&sdata[1], data, sizeof(sdata) - 2); + md_calc_check_sum(sdata, sizeof(sdata) - 1); + smsg_push(sdata, sizeof(sdata)); +} + +void md_send_consumer(uint8_t *data) { + uint8_t sdata[MD_SND_CMD_CONSUMER_LEN + 2] = {0x00}; + + sdata[0] = MD_SND_CMD_SEND_CONSUMER; + memcpy(&sdata[1], data, sizeof(sdata) - 2); + md_calc_check_sum(sdata, sizeof(sdata) - 1); + smsg_push(sdata, sizeof(sdata)); +} + +void md_send_system(uint8_t *data) { + uint8_t sdata[MD_SND_CMD_SYSTEM_LEN + 2] = {0x00}; + + sdata[0] = MD_SND_CMD_SEND_SYSTEM; + memcpy(&sdata[1], data, sizeof(sdata) - 2); + md_calc_check_sum(sdata, sizeof(sdata) - 1); + smsg_push(sdata, sizeof(sdata)); +} + +void md_send_fn(uint8_t *data) { + uint8_t sdata[MD_SND_CMD_FN_LEN + 2] = {0x00}; + + sdata[0] = MD_SND_CMD_SEND_FN; + memcpy(&sdata[1], data, sizeof(sdata) - 2); + md_calc_check_sum(sdata, sizeof(sdata) - 1); + smsg_push(sdata, sizeof(sdata)); +} + +void md_send_mouse(uint8_t *data) { + uint8_t sdata[MD_SND_CMD_MOUSE_LEN + 2] = {0x00}; + + sdata[0] = MD_SND_CMD_SEND_MOUSE; + memcpy(&sdata[1], data, sizeof(sdata) - 2); + md_calc_check_sum(sdata, sizeof(sdata) - 1); + smsg_push(sdata, sizeof(sdata)); +} + +void md_send_devinfo(const char *name) { + uint8_t sdata[MD_SND_CMD_DEVINFO_LEN + 3] = {0x00}; + uint8_t infolen = strlen((const char *)name); + + if (infolen > MD_SND_CMD_DEVINFO_LEN) { + return; + } + + sdata[0] = MD_SND_CMD_SEND_DEVINFO; + sdata[1] = infolen; + + memcpy(&sdata[2], name, infolen); + md_calc_check_sum(sdata, infolen + 2); + smsg_push(sdata, sizeof(sdata)); +} + +void md_send_devctrl(uint8_t cmd) { + uint8_t sdata[3] = {0x00}; + + sdata[0] = MD_SND_CMD_DEVCTRL; + memcpy(&sdata[1], &cmd, sizeof(sdata) - 2); + md_calc_check_sum(sdata, sizeof(sdata) - 1); + smsg_push(sdata, sizeof(sdata)); +} + +void md_send_manufacturer(char *str, uint8_t len) { + uint8_t sdata[MD_SND_CMD_MANUFACTURER_LEN + 3] = {0x00}; + + if (len > MD_SND_CMD_MANUFACTURER_LEN) { + return; + } + + sdata[0] = MD_SND_CMD_MANUFACTURER; + sdata[1] = len; + memcpy(&sdata[2], str, len); + md_calc_check_sum(sdata, len + 2); + smsg_push(sdata, len + 3); +} + +void md_send_product(char *str, uint8_t len) { + uint8_t sdata[MD_SND_CMD_PRODUCT_LEN + 3] = {0x00}; + + if (len > MD_SND_CMD_PRODUCT_LEN) { + return; + } + + sdata[0] = MD_SND_CMD_PRODUCT; + sdata[1] = len; + memcpy(&sdata[2], str, len); + md_calc_check_sum(sdata, len + 2); + smsg_push(sdata, len + 3); +} + +void md_send_vpid(uint16_t vid, uint16_t pid) { + uint8_t sdata[4 + 2] = {0x00}; + uint32_t vpid; + + vpid = (pid << 16) | vid; + + sdata[0] = MD_SND_CMD_VPID; + memcpy(&sdata[1], &vpid, sizeof(vpid)); + md_calc_check_sum(sdata, sizeof(sdata) - 1); + smsg_push(sdata, sizeof(sdata)); +} + +void md_send_raw(uint8_t *data, uint8_t length) { + uint8_t sdata[MD_RAW_SIZE + 2] = {0x00}; + + if (length != MD_RAW_SIZE) { + return; + } + + sdata[0] = MD_SND_CMD_RAW; + memcpy(&sdata[1], data, length); + md_calc_check_sum(sdata, sizeof(sdata) - 1); + smsg_push(sdata, sizeof(sdata)); +} + +void md_devs_change(uint8_t devs, bool reset) __attribute__((weak)); +void md_devs_change(uint8_t devs, bool reset) { + + switch (devs) { + case DEVS_USB: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_USB); + } break; + case DEVS_2G4: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_2G4); + if (reset) { + if (md_get_version() < 48) { + md_send_manufacturer(MD_DONGLE_MANUFACTURER, strlen(MD_DONGLE_MANUFACTURER)); + md_send_product(MD_DONGLE_PRODUCT, strlen(MD_DONGLE_PRODUCT)); + } else { // Add Unicode character support starting from v48. + md_send_manufacturer((char *)USBSTR(MD_DONGLE_MANUFACTURER), sizeof(USBSTR(MD_DONGLE_MANUFACTURER))); + md_send_product((char *)USBSTR(MD_DONGLE_PRODUCT), sizeof(USBSTR(MD_DONGLE_PRODUCT))); + } + md_send_vpid(VENDOR_ID, PRODUCT_ID); + md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); + md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); + } + } break; + case DEVS_BT1: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_BT1); + if (reset) { + md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); + md_send_devinfo(MD_BT1_NAME); + md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); + } + } break; + case DEVS_BT2: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_BT2); + if (reset) { + md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); + md_send_devinfo(MD_BT2_NAME); + md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); + } + } break; + case DEVS_BT3: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_BT3); + if (reset) { + md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); + md_send_devinfo(MD_BT3_NAME); + md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); + } + } break; + case DEVS_BT4: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_BT4); + if (reset) { + md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); + md_send_devinfo(MD_BT4_NAME); + md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); + } + } break; + case DEVS_BT5: { + md_send_devctrl(MD_SND_CMD_DEVCTRL_BT5); + if (reset) { + md_send_devctrl(MD_SND_CMD_DEVCTRL_CLEAN); + md_send_devinfo(MD_BT5_NAME); + md_send_devctrl(MD_SND_CMD_DEVCTRL_PAIR); + } + } break; + default: + break; + } +} + +bool md_inquire_bat(void) { + + if (smsg_is_busy()) { + return false; + } + + md_send_devctrl(MD_SND_CMD_DEVCTRL_INQVOL); + + return true; +} diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/module.h b/vendor/edthu-wireless/keyboards/neo/wireless/module.h new file mode 100644 index 0000000..61a8a45 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/module.h @@ -0,0 +1,122 @@ +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// device index +enum { + DEVS_USB = 0, + DEVS_BT1, + DEVS_BT2, + DEVS_BT3, + DEVS_BT4, + DEVS_BT5, + DEVS_2G4, +}; + +enum { + MD_STATE_NONE = 0, + MD_STATE_PAIRING, + MD_STATE_CONNECTED, + MD_STATE_DISCONNECTED, + MD_STATE_REJECT, +}; + +enum { + MD_SND_CMD_KB_LEN = 8, + MD_SND_CMD_NKRO_LEN = 14, + MD_SND_CMD_CONSUMER_LEN = 2, + MD_SND_CMD_SYSTEM_LEN = 1, + MD_SND_CMD_FN_LEN = 1, + MD_SND_CMD_MOUSE_LEN = 5, + MD_SND_CMD_DEVINFO_LEN = 18, + MD_SND_CMD_MANUFACTURER_LEN = 46, + MD_SND_CMD_PRODUCT_LEN = 46, +}; + +enum { + /* send report */ + MD_SND_CMD_SEND_KB = 0xA1, + MD_SND_CMD_SEND_NKRO = 0xA2, + MD_SND_CMD_SEND_CONSUMER = 0xA3, + MD_SND_CMD_SEND_SYSTEM = 0xA4, + MD_SND_CMD_SEND_FN = 0xA5, + MD_SND_CMD_SEND_MOUSE = 0xA8, + MD_SND_CMD_SEND_DEVINFO = 0xA9, + /* Dongle */ + MD_SND_CMD_MANUFACTURER = 0xAB, + MD_SND_CMD_PRODUCT = 0xAC, + MD_SND_CMD_VPID = 0xAD, + MD_SND_CMD_RAW = 0x91, + /* device ctrl */ + MD_SND_CMD_DEVCTRL = 0xA6, + MD_SND_CMD_DEVCTRL_USB = 0x11, + MD_SND_CMD_DEVCTRL_2G4 = 0x30, + MD_SND_CMD_DEVCTRL_BT1 = 0x31, + MD_SND_CMD_DEVCTRL_BT2 = 0x32, + MD_SND_CMD_DEVCTRL_BT3 = 0x33, + MD_SND_CMD_DEVCTRL_BT4 = 0x34, + MD_SND_CMD_DEVCTRL_BT5 = 0x35, + MD_SND_CMD_DEVCTRL_PAIR = 0x51, + MD_SND_CMD_DEVCTRL_CLEAN = 0x52, + MD_SND_CMD_DEVCTRL_INQVOL = 0x53, + MD_SND_CMD_DEVCTRL_SLEEP_INSTANT = 0x54, // reserved + MD_SND_CMD_DEVCTRL_SLEEP_BT_EN = 0x55, // timeout 30min enable in BT mode + MD_SND_CMD_DEVCTRL_SLEEP_BT_DIS = 0x56, // timeout 30min disable in BT mode + MD_SND_CMD_DEVCTRL_SLEEP_2G4_EN = 0x57, // timeout 30min enable in 2.4G mode + MD_SND_CMD_DEVCTRL_SLEEP_2G4_DIS = 0x58, // timeout 30min enable in 2.4G mode + MD_SND_CMD_DEVCTRL_RSV_DEBUG = 0x60, // reserved + MD_SND_CMD_DEVCTRL_RSV_SLEEP = 0x61, // reserved + MD_SND_CMD_DEVCTRL_FORCED_PAIRING_BT = 0x62, // forced pairing, to be used in a factory environment. + MD_SND_CMD_DEVCTRL_FORCED_PAIRING_2G4 = 0x63, // forced pairing, to be used in a factory environment. + MD_SND_CMD_DEVCTRL_CHARGING = 0x64, // battery power control. + MD_SND_CMD_DEVCTRL_CHARGING_STOP = 0x65, // battery power control. + MD_SND_CMD_DEVCTRL_CHARGING_DONE = 0x66, // battery power control. + MD_SND_CMD_DEVCTRL_FW_VERSION = 0x70, // module fw version. + MD_SND_CMD_INVALID_DATA = 0x00, // unused +}; + +enum { + MD_REV_CMD_RAW = 0x81, + MD_REV_CMD_INDICATOR = 0x5A, + MD_REV_CMD_DEVCTRL = 0x5B, + MD_REV_CMD_DEVCTRL_BAT_LOW = 0x21, // unused + MD_REV_CMD_DEVCTRL_BAT_PWROFF = 0x22, // unused + MD_REV_CMD_DEVCTRL_BAT_NORMAL = 0x23, // unused + MD_REV_CMD_DEVCTRL_PAIRING = 0x31, + MD_REV_CMD_DEVCTRL_CONNECTED = 0x32, + MD_REV_CMD_DEVCTRL_DISCONNECTED = 0x33, + MD_REV_CMD_DEVCTRL_DONE = 0x34, // reserved + MD_REV_CMD_DEVCTRL_RECONNECT = 0x35, // reserved + MD_REV_CMD_DEVCTRL_REJECT = 0x36, + MD_REV_CMD_DEVCTRL_UNPAIRED = 0x37, // reserved + MD_REV_CMD_DEVCTRL_MD_WAKEUP = 0x42, // unused + MD_REV_CMD_DEVCTRL_CLS_UART = 0x43, // unused + MD_REV_CMD_BATVOL = 0x5C, + MD_REV_CMD_MD_FW_VERSION = 0x5D, + MD_REV_CMD_HOST_STATE = 0x60, + MD_REV_CMD_HOST_STATE_SUSPEND = 0x00, + MD_REV_CMD_HOST_STATE_RESUME = 0x01, +}; + +void md_init(void); +void md_main_task(void); +void md_send_kb(uint8_t *data); +void md_send_nkro(uint8_t *data); +void md_send_consumer(uint8_t *data); +void md_send_system(uint8_t *data); +void md_send_fn(uint8_t *data); +void md_send_mouse(uint8_t *data); +void md_send_devctrl(uint8_t cmd); +void md_send_manufacturer(char *str, uint8_t len); +void md_send_product(char *str, uint8_t len); +void md_send_vpid(uint16_t vid, uint16_t pid); +void md_send_raw(uint8_t *data, uint8_t length); +bool md_send_pkt(uint8_t *data, uint32_t len); +bool md_receive_process_user(uint8_t *pdata, uint8_t len); +void md_devs_change(uint8_t devs, bool reset); +bool md_inquire_bat(void); +uint8_t md_get_version(void); +uint8_t *md_getp_state(void); +uint8_t *md_getp_bat(void); +uint8_t *md_getp_indicator(void); diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/smsg.c b/vendor/edthu-wireless/keyboards/neo/wireless/smsg.c new file mode 100644 index 0000000..cfa9571 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/smsg.c @@ -0,0 +1,122 @@ +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "smsg.h" +#include + +#ifndef SMSG_NUM +# define SMSG_NUM 40 +#endif + +#ifndef SMSG_PAYLOAD_LEN +# define SMSG_PAYLOAD_LEN 50 +#endif + +#define SMSG_BUF_SIZE (SMSG_NUM * SMSG_PAYLOAD_LEN) +#define END_PTR ((smsg_ptr_t *)&smsg_instance.ptr[SMSG_NUM - 1]) +#define FREE_SPACE ((uint32_t)(&smsg_buffer[SMSG_BUF_SIZE - 1] - smsg_instance.buffer)) + +typedef struct { + uint8_t *head; + uint8_t *tail; +} smsg_ptr_t; + +typedef struct { + smsg_states_t state; + smsg_ptr_t *ptr; + smsg_ptr_t *in_ptr; + smsg_ptr_t *out_ptr; + uint8_t *buffer; +} smsg_t; + +static smsg_ptr_t smsg_ptr[SMSG_NUM]; +static uint8_t smsg_buffer[SMSG_BUF_SIZE]; +static smsg_t smsg_instance; + +void smsg_init(void) { + + smsg_instance.buffer = smsg_buffer; + smsg_instance.ptr = smsg_ptr; + smsg_instance.ptr->head = smsg_instance.buffer; + smsg_instance.ptr->tail = smsg_instance.buffer; + smsg_instance.in_ptr = smsg_instance.ptr; + smsg_instance.out_ptr = smsg_instance.ptr; + smsg_instance.state = smsg_state_free; +} + +bool smsg_push(uint8_t *buf, uint32_t size) { + + if (smsg_instance.in_ptr == END_PTR) { + if (smsg_instance.ptr == smsg_instance.out_ptr) { + return false; + } + } else { + if ((smsg_instance.in_ptr + 1) == smsg_instance.out_ptr) { + return false; + } + } + + if (FREE_SPACE < SMSG_PAYLOAD_LEN) { + smsg_instance.buffer = smsg_buffer; + } + + if (size > SMSG_PAYLOAD_LEN) { + return false; + } + + memcpy(smsg_instance.buffer, buf, size); + smsg_instance.in_ptr->head = smsg_instance.buffer; + smsg_instance.buffer += size; + smsg_instance.in_ptr->tail = smsg_instance.buffer; + if (smsg_instance.in_ptr == END_PTR) { + smsg_instance.in_ptr = smsg_instance.ptr; + } else { + smsg_instance.in_ptr++; + } + + return true; +} + +uint32_t smsg_peek(uint8_t *buf) { + + if (smsg_instance.out_ptr != smsg_instance.in_ptr) { + uint32_t size; + + size = smsg_instance.out_ptr->tail - smsg_instance.out_ptr->head; + memcpy(buf, smsg_instance.out_ptr->head, size); + + return size; + } + + return 0; +} + +void smsg_pop(void) { + + if (smsg_instance.out_ptr != smsg_instance.in_ptr) { + if (smsg_instance.out_ptr == END_PTR) { + smsg_instance.out_ptr = smsg_instance.ptr; + } else { + smsg_instance.out_ptr++; + } + } +} + +smsg_states_t smsg_get_state(void) { + + return smsg_instance.state; +} + +void smsg_set_state(smsg_states_t state) { + + smsg_instance.state = state; +} + +bool smsg_is_busy(void) { + + if (smsg_instance.out_ptr != smsg_instance.in_ptr) { + return true; + } + + return false; +} diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/smsg.h b/vendor/edthu-wireless/keyboards/neo/wireless/smsg.h new file mode 100644 index 0000000..ea08630 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/smsg.h @@ -0,0 +1,22 @@ +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +typedef enum { + smsg_state_free = 0, + smsg_state_busy, + smsg_state_retry, + smsg_state_replied +} smsg_states_t; + +void smsg_init(void); +bool smsg_push(uint8_t *buf, uint32_t size); +uint32_t smsg_peek(uint8_t *buf); +void smsg_pop(void); +smsg_states_t smsg_get_state(void); +void smsg_set_state(smsg_states_t state); +bool smsg_is_busy(void); diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/transport.c b/vendor/edthu-wireless/keyboards/neo/wireless/transport.c new file mode 100644 index 0000000..09bb8d5 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/transport.c @@ -0,0 +1,167 @@ +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "quantum.h" +#include "module.h" +#include "usb_main.h" +#include "transport.h" + +#ifndef USB_POWER_DOWN_DELAY +# define USB_POWER_DOWN_DELAY 3000 +#endif + +extern host_driver_t chibios_driver; +extern host_driver_t wireless_driver; + +static transport_t transport = TRANSPORT_USB; + +void wls_transport_enable(bool enable) __attribute__((weak)); +void wls_transport_enable(bool enable) { + + if (enable) { + if (host_get_driver() != &wireless_driver) { + host_set_driver(&wireless_driver); + usb_device_state_set_protocol(true); //keyboard_protocol = true; // default with true + } + } else { + if (*md_getp_state() == MD_STATE_CONNECTED) { + wireless_driver.send_keyboard(NULL); + wireless_driver.send_nkro(NULL); + } + } +} + +/* Control USB device connection and disconnection by + * controlling the power supply of the USB DP pull-up resistor. + * Overwrite these two functions. */ +void usb_power_connect(void) __attribute__((weak)); +void usb_power_connect(void) {} + +void usb_power_disconnect(void) __attribute__((weak)); +void usb_power_disconnect(void) {} + +void usb_transport_enable(bool enable) __attribute__((weak)); +void usb_transport_enable(bool enable) { + + if (enable) { + if (host_get_driver() != &chibios_driver) { + extern bool last_suspend_state; + + /* This flag is not set to 1 with probability after usb restart */ + last_suspend_state = true; +#if !defined(KEEP_USB_CONNECTION_IN_WIRELESS_MODE) + usb_power_connect(); + restart_usb_driver(&USBD1); +#endif + host_set_driver(&chibios_driver); + } + } else { + if (USB_DRIVER.state == USB_ACTIVE) { + report_keyboard_t empty_report = {0}; + report_nkro_t empty_nkro_report = {0}; + host_keyboard_send(&empty_report); + host_nkro_send(&empty_nkro_report); + } + +#if !defined(KEEP_USB_CONNECTION_IN_WIRELESS_MODE) + usbStop(&USBD1); + usbDisconnectBus(&USBD1); + usb_power_disconnect(); +#endif + } +} + +void set_transport(transport_t new_transport) { + + transport = new_transport; + + switch (transport) { + case TRANSPORT_USB: { + usb_transport_enable(true); + wls_transport_enable(false); + } break; + case TRANSPORT_WLS: { + wls_transport_enable(true); + usb_transport_enable(false); + } break; + default: + break; + } +} + +transport_t get_transport(void) { + + return transport; +} + +void usb_remote_wakeup(void) { + +#ifdef USB_REMOTE_USE_QMK + if (USB_DRIVER.state == USB_SUSPENDED) { + dprintln("suspending keyboard"); + while (USB_DRIVER.state == USB_SUSPENDED) { + /* Do this in the suspended state */ + suspend_power_down(); // on AVR this deep sleeps for 15ms + /* Remote wakeup */ + if ((USB_DRIVER.status & 2U) && suspend_wakeup_condition()) { + usbWakeupHost(&USB_DRIVER); +# if USB_SUSPEND_WAKEUP_DELAY > 0 + // Some hubs, kvm switches, and monitors do + // weird things, with USB device state bouncing + // around wildly on wakeup, yielding race + // conditions that can corrupt the keyboard state. + // + // Pause for a while to let things settle... + wait_ms(USB_SUSPEND_WAKEUP_DELAY); +# endif + } + } + /* Woken up */ + } +#else + static uint32_t suspend_timer = 0x00; + + if ((USB_DRIVER.state == USB_SUSPENDED)) { + if (!suspend_timer) suspend_timer = sync_timer_read32(); + if (sync_timer_elapsed32(suspend_timer) >= USB_POWER_DOWN_DELAY) { + suspend_timer = 0x00; + suspend_power_down(); + } + } else { + suspend_timer = 0x00; + } +#endif +} + +#ifndef USB_REMOTE_USE_QMK +void usb_remote_host(void) { + + if (USB_DRIVER.state == USB_SUSPENDED) { + if ((USB_DRIVER.status & 2U) && suspend_wakeup_condition()) { + usbWakeupHost(&USB_DRIVER); +# if USB_SUSPEND_WAKEUP_DELAY > 0 + // Some hubs, kvm switches, and monitors do + // weird things, with USB device state bouncing + // around wildly on wakeup, yielding race + // conditions that can corrupt the keyboard state. + // + // Pause for a while to let things settle... + wait_ms(USB_SUSPEND_WAKEUP_DELAY); +# endif + } +# if !defined(USB_REMOTE_USE_QMK) && USB_POWER_DOWN_DELAY + suspend_wakeup_init(); +# endif + } +} + +bool process_action_kb(keyrecord_t *record) { + + (void)record; + if (get_transport() == TRANSPORT_USB){ + usb_remote_host(); + } + + return true; +} +#endif diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/transport.h b/vendor/edthu-wireless/keyboards/neo/wireless/transport.h new file mode 100644 index 0000000..e6ff663 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/transport.h @@ -0,0 +1,18 @@ +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +typedef enum { + TRANSPORT_NONE, + TRANSPORT_USB, + TRANSPORT_WLS, +} transport_t; + +void wls_transport_enable(bool enable); +void usb_transport_enable(bool enable); +void set_transport(transport_t new_transport); +transport_t get_transport(void); +void usb_power_connect(void); +void usb_power_disconnect(void); +void usb_remote_wakeup(void); diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/wireless.c b/vendor/edthu-wireless/keyboards/neo/wireless/wireless.c new file mode 100644 index 0000000..5358b9e --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/wireless.c @@ -0,0 +1,266 @@ + +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "quantum.h" +#include "wireless.h" + +#ifndef WLS_INQUIRY_BAT_TIME +# define WLS_INQUIRY_BAT_TIME 3000 +#endif + +#ifndef WLS_KEYBOARD_REPORT_KEYS +# define WLS_KEYBOARD_REPORT_KEYS KEYBOARD_REPORT_KEYS +#endif + +static uint8_t wls_devs = DEVS_USB; + +void last_matrix_activity_trigger(void); + +uint8_t wireless_keyboard_leds(void); +void wireless_send_keyboard(report_keyboard_t *report); +void wireless_send_nkro(report_nkro_t *report); +void wireless_send_mouse(report_mouse_t *report); +void wireless_send_extra(report_extra_t *report); + +host_driver_t wireless_driver = { + .keyboard_leds = wireless_keyboard_leds, + .send_keyboard = wireless_send_keyboard, + .send_nkro = wireless_send_nkro, + .send_mouse = wireless_send_mouse, + .send_extra = wireless_send_extra, +}; + +void wireless_init(void) { + + md_init(); +} + +uint8_t wireless_keyboard_leds(void) __attribute__((weak)); +uint8_t wireless_keyboard_leds(void) { + + if (*md_getp_state() == MD_STATE_CONNECTED) { + return *md_getp_indicator(); + } + + return 0; +} + +void wireless_send_keyboard(report_keyboard_t *report) __attribute__((weak)); +void wireless_send_keyboard(report_keyboard_t *report) { + uint8_t wls_report_kb[MD_SND_CMD_KB_LEN] = {0}; + + if (*md_getp_state() != MD_STATE_CONNECTED) { + wireless_devs_change(wls_devs, wls_devs, false); + return; + } + + _Static_assert((MD_SND_CMD_KB_LEN) - (WLS_KEYBOARD_REPORT_KEYS) >= 2, "WLS_KEYBOARD_REPORT_KEYS cannot be greater than MD_SND_CMD_KB_LEN - 2."); + + if (report != NULL) { + memcpy(wls_report_kb, (uint8_t *)&report->mods, WLS_KEYBOARD_REPORT_KEYS + 2); + } + + md_send_kb(wls_report_kb); +} + +void wireless_send_nkro(report_nkro_t *report) __attribute__((weak)); +void wireless_send_nkro(report_nkro_t *report) { + static report_keyboard_t temp_report_keyboard = {0}; + uint8_t wls_report_nkro[MD_SND_CMD_NKRO_LEN] = {0}; + +#ifdef NKRO_ENABLE + if (*md_getp_state() != MD_STATE_CONNECTED) { + wireless_devs_change(wls_devs, wls_devs, false); + return; + } + + if (report != NULL) { + report_nkro_t temp_report_nkro = *report; + uint8_t key_count = 0; + + temp_report_keyboard.mods = temp_report_nkro.mods; + for (uint8_t i = 0; i < NKRO_REPORT_BITS; i++) { + key_count += __builtin_popcount(temp_report_nkro.bits[i]); + } + + // find key up and del it. + uint8_t nkro_keys = key_count; + for (uint8_t i = 0; i < WLS_KEYBOARD_REPORT_KEYS && temp_report_keyboard.keys[i]; i++) { + report_nkro_t found_report_nkro; + uint8_t usageid = 0x00; + uint8_t n; + + found_report_nkro = temp_report_nkro; + + for (uint8_t c = 0; c < nkro_keys; c++) { + for (n = 0; n < NKRO_REPORT_BITS && !found_report_nkro.bits[n]; n++) {} + usageid = (n << 3) | biton(found_report_nkro.bits[n]); + del_key_bit(&found_report_nkro, usageid); + if (usageid == temp_report_keyboard.keys[i]) { + del_key_bit(&temp_report_nkro, usageid); + nkro_keys--; + break; + } + } + + if (usageid != temp_report_keyboard.keys[i]) { + temp_report_keyboard.keys[i] = 0x00; + } + } + + /* + * Use NKRO for sending when more than 6 keys are pressed + * to solve the issue of the lack of a protocol flag in wireless mode. + */ + + temp_report_nkro = *report; + + for (uint8_t i = 0; i < key_count; i++) { + uint8_t usageid; + uint8_t idx, n = 0; + + for (n = 0; n < NKRO_REPORT_BITS && !temp_report_nkro.bits[n]; n++) {} + usageid = (n << 3) | biton(temp_report_nkro.bits[n]); + del_key_bit(&temp_report_nkro, usageid); + + for (idx = 0; idx < WLS_KEYBOARD_REPORT_KEYS; idx++) { + if (temp_report_keyboard.keys[idx] == usageid) { + break; + } + if (temp_report_keyboard.keys[idx] == 0x00) { + temp_report_keyboard.keys[idx] = usageid; + break; + } + } + + if (idx == WLS_KEYBOARD_REPORT_KEYS && (usageid < (MD_SND_CMD_NKRO_LEN * 8))) { + wls_report_nkro[usageid / 8] |= 0x01 << (usageid % 8); + } + } + } else { + memset(&temp_report_keyboard, 0, sizeof(temp_report_keyboard)); + } +#endif + + wireless_driver.send_keyboard(&temp_report_keyboard); + md_send_nkro(wls_report_nkro); +} + +void wireless_send_mouse(report_mouse_t *report) __attribute__((weak)); +void wireless_send_mouse(report_mouse_t *report) { + typedef struct { + uint8_t buttons; + int8_t x; + int8_t y; + int8_t v; + int8_t h; + } __attribute__((packed)) wls_report_mouse_t; + + wls_report_mouse_t wls_report_mouse = {0}; + + if (*md_getp_state() != MD_STATE_CONNECTED) { + wireless_devs_change(wls_devs, wls_devs, false); + return; + } + + if (report != NULL) { + wls_report_mouse.buttons = report->buttons; + wls_report_mouse.x = report->x; + wls_report_mouse.y = report->y; + wls_report_mouse.v = report->v; + wls_report_mouse.h = report->h; + } + + md_send_mouse((uint8_t *)&wls_report_mouse); +} + +void wireless_send_extra(report_extra_t *report) __attribute__((weak)); +void wireless_send_extra(report_extra_t *report) { + uint16_t usage = 0; + + if (*md_getp_state() != MD_STATE_CONNECTED) { + wireless_devs_change(wls_devs, wls_devs, false); + return; + } + + if (report != NULL) { + usage = report->usage; + + switch (usage) { + case 0x81: + case 0x82: + case 0x83: { // system usage + usage = 0x01 << (usage - 0x81); + md_send_system((uint8_t *)&usage); + } break; + default: { + md_send_consumer((uint8_t *)&usage); + } break; + } + } +} + +void wireless_devs_change_user(uint8_t old_devs, uint8_t new_devs, bool reset) __attribute__((weak)); +void wireless_devs_change_user(uint8_t old_devs, uint8_t new_devs, bool reset) {} + +void wireless_devs_change_kb(uint8_t old_devs, uint8_t new_devs, bool reset) __attribute__((weak)); +void wireless_devs_change_kb(uint8_t old_devs, uint8_t new_devs, bool reset) {} + +void wireless_devs_change(uint8_t old_devs, uint8_t new_devs, bool reset) { + bool changed = (old_devs == DEVS_USB) ? (new_devs != DEVS_USB) : (new_devs == DEVS_USB); + + if (changed) { + set_transport((new_devs != DEVS_USB) ? TRANSPORT_WLS : TRANSPORT_USB); + } + + if ((wls_devs != new_devs) || reset) { + *md_getp_state() = MD_STATE_DISCONNECTED; + *md_getp_indicator() = 0; + } + + wls_devs = new_devs; + last_matrix_activity_trigger(); + + md_devs_change(new_devs, reset); + wireless_devs_change_kb(old_devs, new_devs, reset); + wireless_devs_change_user(old_devs, new_devs, reset); +} + +uint8_t wireless_get_current_devs(void) { + return wls_devs; +} + +void wireless_pre_task(void) __attribute__((weak)); +void wireless_pre_task(void) {} + +void wireless_post_task(void) __attribute__((weak)); +void wireless_post_task(void) {} + +void wireless_task(void) { + + wireless_pre_task(); + lpwr_task(); + md_main_task(); + wireless_post_task(); + + /* usb_remote_wakeup() should be invoked last so that we have chance + * to switch to wireless after start-up when usb is not connected + */ + if (get_transport() == TRANSPORT_USB) { + usb_remote_wakeup(); + } else if (lpwr_get_state() == LPWR_NORMAL) { + static uint32_t inqtimer = 0x00; + + if (sync_timer_elapsed32(inqtimer) >= (WLS_INQUIRY_BAT_TIME)) { + if (md_inquire_bat()) { + inqtimer = sync_timer_read32(); + } + } + } +} + +void housekeeping_task_kb(void) { + + wireless_task(); +} diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/wireless.h b/vendor/edthu-wireless/keyboards/neo/wireless/wireless.h new file mode 100644 index 0000000..683347b --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/wireless.h @@ -0,0 +1,14 @@ +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "transport.h" +#include "lowpower.h" +#include "module.h" + +void wireless_init(void); +void wireless_devs_change(uint8_t old_devs, uint8_t new_devs, bool reset); +uint8_t wireless_get_current_devs(void); +void wireless_pre_task(void); +void wireless_post_task(void); diff --git a/vendor/edthu-wireless/keyboards/neo/wireless/wireless.mk b/vendor/edthu-wireless/keyboards/neo/wireless/wireless.mk new file mode 100644 index 0000000..5076e54 --- /dev/null +++ b/vendor/edthu-wireless/keyboards/neo/wireless/wireless.mk @@ -0,0 +1,26 @@ +WIRELESS_ENABLE ?= yes +WIRELESS_DIR = $(TOP_DIR)/keyboards/neo/wireless + +ifeq ($(strip $(WIRELESS_ENABLE)), yes) + OPT_DEFS += -DWIRELESS_ENABLE -DNO_USB_STARTUP_CHECK + + OPT_DEFS += -include $(WIRELESS_DIR)/md_raw.h + + UART_DRIVER_REQUIRED ?= yes + WIRELESS_LPWR_STOP_ENABLE ?= yes + + VPATH += $(WIRELESS_DIR) + + SRC += \ + $(WIRELESS_DIR)/wireless.c \ + $(WIRELESS_DIR)/transport.c \ + $(WIRELESS_DIR)/lowpower.c \ + $(WIRELESS_DIR)/md_raw.c \ + $(WIRELESS_DIR)/smsg.c \ + $(WIRELESS_DIR)/module.c + + ifeq ($(strip $(WIRELESS_LPWR_STOP_ENABLE)), yes) + OPT_DEFS += -DWIRELESS_LPWR_STOP_ENABLE + SRC += $(WIRELESS_DIR)/lpwr_wb32.c + endif +endif