diff --git a/keyboards/linker/wireless/lowpower.c b/keyboards/linker/wireless/lowpower.c new file mode 100644 index 00000000000..8f4a72e33ff --- /dev/null +++ b/keyboards/linker/wireless/lowpower.c @@ -0,0 +1,304 @@ +// Copyright 2024 Su (@isuua) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "quantum.h" +#include "lowpower.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) { + 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/linker/wireless/lowpower.h b/keyboards/linker/wireless/lowpower.h new file mode 100644 index 00000000000..e7d60a416d0 --- /dev/null +++ b/keyboards/linker/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/linker/wireless/lpwr_wb32.c b/keyboards/linker/wireless/lpwr_wb32.c new file mode 100644 index 00000000000..8cfacae61d9 --- /dev/null +++ b/keyboards/linker/wireless/lpwr_wb32.c @@ -0,0 +1,197 @@ +// 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) { + setPinOutputOpenDrain(col_pins[i]); + writePinLow(col_pins[i]); + } + } + + for (uint8_t i = 0; i < ARRAY_SIZE(row_pins); i++) { + if (row_pins[i] != NO_PIN) { + setPinInputHigh(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) { + setPinOutputOpenDrain(row_pins[i]); + writePinLow(row_pins[i]); + } + } + + for (uint8_t i = 0; i < ARRAY_SIZE(col_pins); i++) { + if (col_pins[i] != NO_PIN) { + setPinInputHigh(col_pins[i]); + waitInputPinDelay(); + palEnableLineEvent(col_pins[i], PAL_EVENT_MODE_BOTH_EDGES); + } + } +#endif + +#ifndef LPWR_UART_WAKEUP_DISABLE + setPinInput(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 + +#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/linker/wireless/md_raw.c b/keyboards/linker/wireless/md_raw.c new file mode 100644 index 00000000000..eebfc94d9f4 --- /dev/null +++ b/keyboards/linker/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/linker/wireless/md_raw.h b/keyboards/linker/wireless/md_raw.h new file mode 100644 index 00000000000..597da13eb21 --- /dev/null +++ b/keyboards/linker/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_461 replaced_hid_send // via.c diff --git a/keyboards/linker/wireless/module.c b/keyboards/linker/wireless/module.c new file mode 100644 index 00000000000..76a0556490c --- /dev/null +++ b/keyboards/linker/wireless/module.c @@ -0,0 +1,526 @@ +// 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_RAW: + 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; + 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; + } + + // raw data + if ((md_rev_payload[0] == MD_REV_CMD_RAW) && (md_rev_payload[1] == MD_REV_CMD_RAW_OUT)) { + md_rev_payload[data_count++] = data; + data_remain = data + 1; + continue; + } + } + 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; + uint8_t len; + + len = md_rev_payload[2]; + pdata = &md_rev_payload[3]; + + if (len == sizeof(md_raw_payload)) { + memcpy(md_raw_payload, pdata, len); + md_receive_raw_cb(md_raw_payload, len); + } + } 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) { + md_send_pkt(md_pkt_payload, size); + smsg_timer = sync_timer_read32(); + smsg_set_state(smsg_state_busy); + } + } 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; +} + +void md_send_pkt(uint8_t *data, uint32_t len) { + + if (!data || !len) { + return; + } + + // send + uart_transmit(data, len); +} + +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 + 4] = {0x00}; + + if (length != MD_RAW_SIZE) { + return; + } + + sdata[0] = MD_SND_CMD_RAW; + sdata[1] = MD_SND_CMD_RAW_IN; + sdata[2] = length; + memcpy(&sdata[3], 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/linker/wireless/module.h b/keyboards/linker/wireless/module.h new file mode 100644 index 00000000000..aa389b863a5 --- /dev/null +++ b/keyboards/linker/wireless/module.h @@ -0,0 +1,124 @@ +// 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 = 0xAF, + MD_SND_CMD_RAW_IN = 0x61, + /* 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 = 0xAF, + MD_REV_CMD_RAW_OUT = 0x60, + 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); +void 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/linker/wireless/smsg.c b/keyboards/linker/wireless/smsg.c new file mode 100644 index 00000000000..cfa9571888a --- /dev/null +++ b/keyboards/linker/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/linker/wireless/smsg.h b/keyboards/linker/wireless/smsg.h new file mode 100644 index 00000000000..ea08630ed6d --- /dev/null +++ b/keyboards/linker/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/linker/wireless/transport.c b/keyboards/linker/wireless/transport.c new file mode 100644 index 00000000000..6d95bbe9e2b --- /dev/null +++ b/keyboards/linker/wireless/transport.c @@ -0,0 +1,116 @@ +// 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" + +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); + 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) { + chibios_driver.send_keyboard(NULL); + chibios_driver.send_nkro(NULL); + } + +#if !defined(KEEP_USB_CONNECTION_IN_WIRELESS_MODE) + usbStop(&USBD1); + usbDisconnectBus(&USBD1); + usb_power_disconnect(); +#endif + } +} + +void set_transport(transport_t new_transport) { + + if (transport != 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) { + + 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 */ + } +} diff --git a/keyboards/linker/wireless/transport.h b/keyboards/linker/wireless/transport.h new file mode 100644 index 00000000000..e6ff6637c8f --- /dev/null +++ b/keyboards/linker/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/linker/wireless/wireless.c b/keyboards/linker/wireless/wireless.c new file mode 100644 index 00000000000..c50aad36dc7 --- /dev/null +++ b/keyboards/linker/wireless/wireless.c @@ -0,0 +1,255 @@ + +// 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 + +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) { + + if (*md_getp_state() == MD_STATE_CONNECTED) { + return *md_getp_indicator(); + } + + return 0; +} + +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; + } + + if (report != NULL) { + memcpy(wls_report_kb, (uint8_t *)report, sizeof(wls_report_kb)); + } + md_send_kb(wls_report_kb); +} + +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}; + + 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. + for (uint8_t i = 0; i < KEYBOARD_REPORT_KEYS && temp_report_keyboard.keys[i]; i++) { + uint8_t usageid = 0x00; + uint8_t n; + + for (uint8_t c = 0; c < key_count; c++) { + 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); + if (usageid == temp_report_keyboard.keys[i]) { + 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 < 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 == 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)); + } + + wireless_driver.send_keyboard(&temp_report_keyboard); + md_send_nkro(wls_report_nkro); +} + +void wireless_send_mouse(report_mouse_t *report) { + typedef struct { + uint8_t buttons; + int8_t x; + int8_t y; + int8_t z; + 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.z = report->h; + wls_report_mouse.h = report->v; + } + + md_send_mouse((uint8_t *)&wls_report_mouse); +} + +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; +} + +bool lpwr_is_allow_timeout_hook(void) { + + if (wls_devs == DEVS_USB) { + return false; + } + + return true; +} + +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/linker/wireless/wireless.h b/keyboards/linker/wireless/wireless.h new file mode 100644 index 00000000000..683347b297c --- /dev/null +++ b/keyboards/linker/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/linker/wireless/wireless.mk b/keyboards/linker/wireless/wireless.mk new file mode 100644 index 00000000000..f01aa55675f --- /dev/null +++ b/keyboards/linker/wireless/wireless.mk @@ -0,0 +1,26 @@ +WIRELESS_ENABLE ?= yes +WIRELESS_DIR = $(TOP_DIR)/keyboards/linker/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