diff --git a/keyboards/lemokey/common/factory_test.c b/keyboards/lemokey/common/factory_test.c new file mode 100644 index 00000000000..b376165e1cf --- /dev/null +++ b/keyboards/lemokey/common/factory_test.c @@ -0,0 +1,425 @@ +/* Copyright 2021 @ Keychron (https://www.keychron.com) + * + * 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" +#include "raw_hid.h" +#include "via.h" + +#include "lemokey_task.h" +#ifdef LK_WIRELESS_ENABLE +# include "transport.h" +# include "battery.h" +# include "lpm.h" +# include "lkbt51.h" +# include "indicator.h" +#endif +#include "config.h" +#include "version.h" + +#ifndef RAW_EPSIZE +# define RAW_EPSIZE 32 +#endif + +#ifndef BL_CYCLE_KEY +# define BL_CYCLE_KEY KC_RIGHT +#endif + +#ifndef BL_TRIG_KEY +# define BL_TRIG_KEY KC_HOME +#endif + +#ifndef P2P4G_CELAR_MASK +# define P2P4G_CELAR_MASK P2P4G_CLEAR_PAIRING_TYPE_C +#endif + +enum { + BACKLIGHT_TEST_OFF = 0, + BACKLIGHT_TEST_WHITE, + BACKLIGHT_TEST_RED, + BACKLIGHT_TEST_GREEN, + BACKLIGHT_TEST_BLUE, + BACKLIGHT_TEST_MAX, +}; + +enum { + KEY_PRESS_FN = 0x01 << 0, + KEY_PRESS_J = 0x01 << 1, + KEY_PRESS_Z = 0x01 << 2, + KEY_PRESS_BL_KEY1 = 0x01 << 3, + KEY_PRESS_BL_KEY2 = 0x01 << 4, + KEY_PRESS_FACTORY_RESET = KEY_PRESS_FN | KEY_PRESS_J | KEY_PRESS_Z, + KEY_PRESS_BACKLIGTH_TEST = KEY_PRESS_FN | KEY_PRESS_BL_KEY1 | KEY_PRESS_BL_KEY2, +}; + +enum { + FACTORY_TEST_CMD_BACKLIGHT = 0x01, + FACTORY_TEST_CMD_OS_SWITCH, + FACTORY_TEST_CMD_JUMP_TO_BL, + FACTORY_TEST_CMD_INT_PIN, + FACTORY_TEST_CMD_GET_TRANSPORT, + FACTORY_TEST_CMD_CHARGING_ADC, + FACTORY_TEST_CMD_RADIO_CARRIER, + FACTORY_TEST_CMD_GET_BUILD_TIME, +}; + +enum { + P2P4G_CLEAR_PAIRING_TYPE_A = 0x01 << 0, + P2P4G_CLEAR_PAIRING_TYPE_C = 0x01 << 1, +}; + +enum { + OS_SWITCH = 0x01, +}; + +static uint32_t factory_reset_timer = 0; +static uint8_t factory_reset_state = 0; +static uint8_t backlight_test_mode = BACKLIGHT_TEST_OFF; + +static uint32_t factory_reset_ind_timer = 0; +static uint8_t factory_reset_ind_state = 0; +static bool report_os_sw_state = false; +static bool keys_released = true; + +void factory_timer_start(void) { + factory_reset_timer = timer_read32(); +} + +static inline void factory_timer_check(void) { + if (timer_elapsed32(factory_reset_timer) > 3000) { + factory_reset_timer = 0; + + if (factory_reset_state == KEY_PRESS_FACTORY_RESET) { + factory_reset_ind_timer = timer_read32(); + factory_reset_ind_state++; + keys_released = false; + + clear_keyboard(); // Avoid key being pressed after NKRO state changed + layer_state_t default_layer_tmp = default_layer_state; + eeconfig_init(); + keymap_config.raw = eeconfig_read_keymap(); + default_layer_set(default_layer_tmp); +#ifdef LED_MATRIX_ENABLE + if (!led_matrix_is_enabled()) led_matrix_enable(); + led_matrix_init(); +#endif +#ifdef RGB_MATRIX_ENABLE + if (!rgb_matrix_is_enabled()) rgb_matrix_enable(); + rgb_matrix_init(); +#endif +#ifdef LK_WIRELESS_ENABLE + lkbt51_factory_reset(P2P4G_CELAR_MASK); +#endif + } else if (factory_reset_state == KEY_PRESS_BACKLIGTH_TEST) { +#ifdef LED_MATRIX_ENABLE + if (!led_matrix_is_enabled()) led_matrix_enable(); +#endif +#ifdef RGB_MATRIX_ENABLE + if (!rgb_matrix_is_enabled()) rgb_matrix_enable(); +#endif + backlight_test_mode = BACKLIGHT_TEST_WHITE; + } + + factory_reset_state = 0; + } +} + +static inline void factory_reset_ind_timer_check(void) { + if (factory_reset_ind_timer && timer_elapsed32(factory_reset_ind_timer) > 250) { + if (factory_reset_ind_state++ > 6) { + factory_reset_ind_timer = factory_reset_ind_state = 0; + } else { + factory_reset_ind_timer = timer_read32(); + } + } +} + +bool process_record_factory_test(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { +#if defined(FN_KEY_1) || defined(FN_KEY_2) +# if defined(FN_KEY_1) + case FN_KEY_1: /* fall through */ +# endif +# if defined(FN_KEY_2) + case FN_KEY_2: +# endif +# if defined(FN_KEY_3) + case FN_KEY_3: +# endif + if (record->event.pressed) { + factory_reset_state |= KEY_PRESS_FN; + } else { + factory_reset_state &= ~KEY_PRESS_FN; + factory_reset_timer = 0; + } + break; +#endif + case KC_J: + if (record->event.pressed) { + factory_reset_state |= KEY_PRESS_J; + if (factory_reset_state == 0x07) factory_timer_start(); + if (factory_reset_state & KEY_PRESS_FN) return false; + } else { + factory_reset_state &= ~KEY_PRESS_J; + factory_reset_timer = 0; + } + break; + case KC_Z: +#if defined(FN_Z_KEY) + case FN_Z_KEY: +#endif + if (record->event.pressed) { + factory_reset_state |= KEY_PRESS_Z; + if (factory_reset_state == 0x07) factory_timer_start(); + if ((factory_reset_state & KEY_PRESS_FN) && keycode == KC_Z) return false; + } else { + factory_reset_state &= ~KEY_PRESS_Z; + factory_reset_timer = 0; + /* Avoid changing backlight effect on key released if FN_Z_KEY is mode*/ + + if (!keys_released && keycode >= QK_BACKLIGHT_ON && keycode <= RGB_MODE_TWINKLE) { + keys_released = true; + return false; + } + } + break; +#if defined(BL_CYCLE_KEY) || defined(BL_CYCLE_KEY_2) +# if defined(BL_CYCLE_KEY) + case BL_CYCLE_KEY: +# endif +# if defined(FN_BL_CYCLE_KEY) + case FN_BL_CYCLE_KEY: +# endif + if (record->event.pressed) { + if (backlight_test_mode) { + if (++backlight_test_mode >= BACKLIGHT_TEST_MAX) { + backlight_test_mode = BACKLIGHT_TEST_WHITE; + } + } else { + factory_reset_state |= KEY_PRESS_BL_KEY1; + if (factory_reset_state == 0x19) { + factory_timer_start(); + } + } + } else { + factory_reset_state &= ~KEY_PRESS_BL_KEY1; + factory_reset_timer = 0; + } + break; +#endif +#if defined(BL_TRIG_KEY) || defined(BL_TRIG_KEY_2) +# if defined(BL_TRIG_KEY) + case BL_TRIG_KEY: +# endif +# if defined(FN_BL_TRIG_KEY) + case FN_BL_TRIG_KEY: +# endif + if (record->event.pressed) { + if (backlight_test_mode) { + backlight_test_mode = BACKLIGHT_TEST_OFF; + } else { + factory_reset_state |= KEY_PRESS_BL_KEY2; + if (factory_reset_state == 0x19) { + factory_timer_start(); + } + } + } else { + factory_reset_state &= ~KEY_PRESS_BL_KEY2; + factory_reset_timer = 0; + } + break; +#endif + } + + return true; +} + +#ifdef LED_MATRIX_ENABLE +bool factory_test_indicator(void) { + if (factory_reset_ind_state) { + led_matrix_set_value_all(factory_reset_ind_state % 2 ? 0 : 255); + return false; + } + + return true; +} +#endif + +#ifdef RGB_MATRIX_ENABLE +bool factory_test_indicator(void) { + if (factory_reset_ind_state) { + backlight_test_mode = BACKLIGHT_TEST_OFF; + rgb_matrix_set_color_all(factory_reset_ind_state % 2 ? 0 : 255, 0, 0); + return false; + } else if (backlight_test_mode) { + switch (backlight_test_mode) { + case BACKLIGHT_TEST_WHITE: + rgb_matrix_set_color_all(255, 255, 255); + break; + case BACKLIGHT_TEST_RED: + rgb_matrix_set_color_all(255, 0, 0); + break; + case BACKLIGHT_TEST_GREEN: + rgb_matrix_set_color_all(0, 255, 0); + break; + case BACKLIGHT_TEST_BLUE: + rgb_matrix_set_color_all(0, 0, 255); + break; + } + return false; + } + + return true; +} +#endif + +bool factory_reset_indicating(void) { + return factory_reset_ind_timer; +} + +bool factory_test_task(void) { + if (factory_reset_timer) factory_timer_check(); + if (factory_reset_ind_timer) factory_reset_ind_timer_check(); + + return true; +} + +void factory_test_send(uint8_t *payload, uint8_t length) { +#ifdef RAW_ENABLE + uint16_t checksum = 0; + uint8_t data[RAW_EPSIZE] = {0}; + + uint8_t i = 0; + data[i++] = 0xAB; + + memcpy(&data[i], payload, length); + i += length; + + for (uint8_t i = 1; i < RAW_EPSIZE - 3; i++) + checksum += data[i]; + data[RAW_EPSIZE - 2] = checksum & 0xFF; + data[RAW_EPSIZE - 1] = (checksum >> 8) & 0xFF; + + raw_hid_send(data, RAW_EPSIZE); +#endif +} + +void factory_test_rx(uint8_t *data, uint8_t length) { + if (data[0] == 0xAB) { + uint16_t checksum = 0; + + for (uint8_t i = 1; i < RAW_EPSIZE - 3; i++) { + checksum += data[i]; + } + /* Verify checksum */ + if ((checksum & 0xFF) != data[RAW_EPSIZE - 2] || checksum >> 8 != data[RAW_EPSIZE - 1]) return; + +#ifdef LK_WIRELESS_ENABLE + uint8_t payload[32]; + uint8_t len = 0; +#endif + + switch (data[1]) { + case FACTORY_TEST_CMD_BACKLIGHT: + backlight_test_mode = data[2]; + factory_reset_timer = 0; + break; + case FACTORY_TEST_CMD_OS_SWITCH: + break; + case FACTORY_TEST_CMD_JUMP_TO_BL: + // if (memcmp(&data[2], "JumpToBootloader", strlen("JumpToBootloader")) == 0) bootloader_jump(); + break; +#ifdef LK_WIRELESS_ENABLE + case FACTORY_TEST_CMD_INT_PIN: + switch (data[2]) { + /* Enalbe/disable test */ + case 0xA1: + lkbt51_int_pin_test(data[3]); + break; + /* Set INT state */ + case 0xA2: + writePin(BLUETOOTH_INT_OUTPUT_PIN, data[3]); + break; + } + break; + case FACTORY_TEST_CMD_GET_TRANSPORT: + payload[len++] = FACTORY_TEST_CMD_GET_TRANSPORT; + payload[len++] = get_transport(); + payload[len++] = readPin(USB_POWER_SENSE_PIN); + factory_test_send(payload, len); + break; +#endif +#ifdef BATTERY_CHARGE_DONE_DETECT_ADC + case FACTORY_TEST_CMD_CHARGING_ADC: + case 0xA1: + battery_charging_monitor(data[3]); + break; + case 0xA2: + payload[len++] = FACTORY_TEST_CMD_CHARGING_ADC; + payload[len++] = battery_adc_read_charging_pin(); + factory_test_send(payload, len); + break; +#endif +#ifdef LK_WIRELESS_ENABLE + case FACTORY_TEST_CMD_RADIO_CARRIER: + if (data[2] < 79) lkbt51_radio_test(data[2]); + break; + +# ifdef WERELESS_PRESSURE_TEST + case 0x70: + switch (data[2]) { + /* Enalbe/disable test */ + case 0xB1: + SEND_STRING("abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890\n"); + break; + case 0xB2: + payload[len++] = 0x70; + payload[len++] = 0xB2; + payload[len++] = wireless_get_state(); + factory_test_send(payload, len); + break; + } + break; +# endif +#endif + case FACTORY_TEST_CMD_GET_BUILD_TIME: { + payload[len++] = FACTORY_TEST_CMD_GET_BUILD_TIME; + payload[len++] = 'v'; + if ((DEVICE_VER & 0xF000) != 0) itoa((DEVICE_VER >> 12), (char *)&payload[len++], 16); + itoa((DEVICE_VER >> 8) & 0xF, (char *)&payload[len++], 16); + payload[len++] = '.'; + itoa((DEVICE_VER >> 4) & 0xF, (char *)&payload[len++], 16); + payload[len++] = '.'; + itoa((DEVICE_VER >> 4) & 0xF, (char *)&payload[len++], 16); + payload[len++] = ' '; + memcpy(&payload[len], QMK_BUILDDATE, sizeof(QMK_BUILDDATE)); + len += sizeof(QMK_BUILDDATE); + factory_test_send(payload, len); + } break; + } + } +} + +bool dip_switch_update_user(uint8_t index, bool active) { + if (report_os_sw_state) { +#ifdef INVERT_OS_SWITCH_STATE + active = !active; +#endif + uint8_t payload[3] = {FACTORY_TEST_CMD_OS_SWITCH, OS_SWITCH, active}; + factory_test_send(payload, 3); + } + + return true; +} diff --git a/keyboards/lemokey/common/factory_test.h b/keyboards/lemokey/common/factory_test.h new file mode 100644 index 00000000000..a98d10043c8 --- /dev/null +++ b/keyboards/lemokey/common/factory_test.h @@ -0,0 +1,34 @@ +/* Copyright 2022 @ lokher (https://www.keychron.com) + * + * 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 FACTORY_RESET_CHECK process_record_factory_test +#define FACTORY_RESET_TASK factory_test_task + +void factory_test_init(void); + +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) +bool factory_test_indicator(void); +#endif + +//void process_record_factory_test(uint16_t keycode, keyrecord_t *record); +bool factory_reset_indicating(void); +void factory_test_task(void); +void factory_test_rx(uint8_t *data, uint8_t length); + +bool process_record_factory_test(uint16_t keycode, keyrecord_t *record); + diff --git a/keyboards/lemokey/common/lemokey_common.c b/keyboards/lemokey/common/lemokey_common.c new file mode 100644 index 00000000000..57104423456 --- /dev/null +++ b/keyboards/lemokey/common/lemokey_common.c @@ -0,0 +1,131 @@ +/* Copyright 2022 @ Keychron (https://www.keychron.com) + * + * 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 +#include "lemokey_common.h" + +#ifdef FACTORY_TEST_ENABLE +# include "factory_test.h" +# include "lemokey_common.h" +#endif + +#ifdef LK_WIRELESS_ENABLE +# include "lkbt51.h" +#endif + +static uint8_t mac_keycode[4] = {KC_LOPT, KC_ROPT, KC_LCMD, KC_RCMD}; + +static key_combination_t key_comb_list[3] = { + {2, {KC_LWIN, KC_TAB}}, + {2, {KC_LWIN, KC_E}}, + {2, {KC_LWIN, KC_L}}, +}; + +bool process_record_lemokey_common(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case KC_TASK_VIEW: + case KC_FILE_EXPLORER: + case KC_LOCK_SCREEN: + if (record->event.pressed) { + for (uint8_t i = 0; i < key_comb_list[keycode - KC_TASK].len; i++) { + register_code(key_comb_list[keycode - KC_TASK].keycode[i]); + } + } else { + for (uint8_t i = 0; i < key_comb_list[keycode - KC_TASK].len; i++) { + unregister_code(key_comb_list[keycode - KC_TASK].keycode[i]); + } + } + return false; // Skip all further processing of this key + case KC_MCTRL: + if (record->event.pressed) { + register_code(KC_MISSION_CONTROL); + } else { + unregister_code(KC_MISSION_CONTROL); + } + return false; // Skip all further processing of this key + case KC_LANCH: + if (record->event.pressed) { + register_code(KC_LAUNCHPAD); + } else { + unregister_code(KC_LAUNCHPAD); + } + return false; // Skip all further processing of this key + case KC_LOPTN: + case KC_ROPTN: + case KC_LCMMD: + case KC_RCMMD: + if (record->event.pressed) { + register_code(mac_keycode[keycode - KC_LOPTN]); + } else { + unregister_code(mac_keycode[keycode - KC_LOPTN]); + } + return false; // Skip all further processing of this key + default: + return true; // Process all other keycodes normally + } +} + +void lemokey_common_task(void) {} + +#ifdef ENCODER_ENABLE +static void encoder0_pad_cb(void *param) { + (void)param; + encoder_inerrupt_read(0); +} + +void encoder_cb_init(void) { + pin_t encoders_pad_a[] = ENCODERS_PAD_A; + pin_t encoders_pad_b[] = ENCODERS_PAD_B; + palEnableLineEvent(encoders_pad_a[0], PAL_EVENT_MODE_BOTH_EDGES); + palEnableLineEvent(encoders_pad_b[0], PAL_EVENT_MODE_BOTH_EDGES); + palSetLineCallback(encoders_pad_a[0], encoder0_pad_cb, NULL); + palSetLineCallback(encoders_pad_b[0], encoder0_pad_cb, NULL); +} +#endif + +//__attribute__((weak)) bool raw_hid_receive_lemokey(uint8_t *data, uint8_t length) { return true; } + +bool via_command_kb(uint8_t *data, uint8_t length) { + // if (!raw_hid_receive_lemokey(data, length)) + // return false; + + switch (data[0]) { +#ifdef LK_WIRELESS_ENABLE + case 0xAA: + lkbt51_dfu_rx(data, length); + break; +#endif +#ifdef FACTORY_TEST_ENABLE + case 0xAB: + factory_test_rx(data, length); + break; +#endif + default: + return false; + } + + return true; +} + +#if !defined(VIA_ENABLE) +void raw_hid_receive(uint8_t *data, uint8_t length) { + switch (data[0]) { + case RAW_HID_CMD: + via_command_kb(data, length); + break; + } +} +#endif diff --git a/keyboards/lemokey/common/lemokey_common.h b/keyboards/lemokey/common/lemokey_common.h new file mode 100644 index 00000000000..81bed69a93b --- /dev/null +++ b/keyboards/lemokey/common/lemokey_common.h @@ -0,0 +1,64 @@ +/* Copyright 2022 @ Keychron (https://www.lemokey.com) + * + * 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 "stdint.h" + +// clang-format off +enum { + KC_TASK_VIEW = QK_KB_0, + KC_FILE_EXPLORER, + KC_LOCK_SCREEN, // Lock screen + KC_MCTRL, + KC_LANCH, + KC_LOPTN, + KC_ROPTN, + KC_LCMMD, + KC_RCMMD, +#ifdef LK_WIRELESS_ENABLE + BT_HST1, + BT_HST2, + BT_HST3, + P2P4G, + BAT_LVL, +#else + BT_HST1 = _______, + BT_HST2 = _______, + BT_HST3 = _______, + P2P4G = _______, + BAT_LVL = _______, +#endif + + NEW_SAFE_RANGE, +}; + +#define KC_TASK KC_TASK_VIEW +#define KC_FILE KC_FILE_EXPLORER +#define KC_LOCK KC_LOCK_SCREEN + +typedef struct PACKED { + uint8_t len; + uint8_t keycode[2]; +} key_combination_t; + +bool process_record_lemokey_common(uint16_t keycode, keyrecord_t *record); +void lemokey_common_task(void); + +#ifdef ENCODER_ENABLE +void encoder_cb_init(void); +#endif + diff --git a/keyboards/lemokey/common/lemokey_common.mk b/keyboards/lemokey/common/lemokey_common.mk new file mode 100644 index 00000000000..98769ad1ad0 --- /dev/null +++ b/keyboards/lemokey/common/lemokey_common.mk @@ -0,0 +1,10 @@ +OPT_DEFS += -DFACTORY_TEST_ENABLE + +LEMOKEY_COMMON_DIR = common +SRC += \ + $(LEMOKEY_COMMON_DIR)/lemokey_task.c \ + $(LEMOKEY_COMMON_DIR)/lemokey_common.c \ + $(LEMOKEY_COMMON_DIR)/factory_test.c + +VPATH += $(TOP_DIR)/keyboards/lemokey/$(LEMOKEY_COMMON_DIR) + diff --git a/keyboards/lemokey/common/lemokey_task.c b/keyboards/lemokey/common/lemokey_task.c new file mode 100644 index 00000000000..53146b18276 --- /dev/null +++ b/keyboards/lemokey/common/lemokey_task.c @@ -0,0 +1,117 @@ +/* Copyright 2023 @ Keychron (https://www.keychron.com) + * + * 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 +#include "lemokey_task.h" +#include "quantum.h" +#include "lemokey_common.h" +#ifdef FACTORY_TEST_ENABLE +# include "factory_test.h" +#endif + +__attribute__((weak)) bool process_record_lemokey_kb(uint16_t keycode, keyrecord_t *record) { + return true; +} + +bool process_record_lemokey(uint16_t keycode, keyrecord_t *record) { +#ifdef LK_WIRELESS_ENABLE + extern bool process_record_wireless(uint16_t keycode, keyrecord_t * record); + if (!process_record_wireless(keycode, record)) return false; +#endif +#ifdef FACTORY_TEST_ENABLE + if (!process_record_factory_test(keycode, record)) return false; +#endif + // extern bool process_record_lemokey_kb(uint16_t keycode, keyrecord_t *record); + + if (!process_record_lemokey_kb(keycode, record)) return false; + + return true; +} + +#if defined(LED_MATRIX_ENABLE) +bool led_matrix_indicators_lemokey(void) { +# ifdef LK_WIRELESS_ENABLE + extern bool led_matrix_indicators_bt(void); + led_matrix_indicators_bt(); +# endif +# ifdef FACTORY_TEST_ENABLE + factory_test_indicator(); +# endif + return true; +} +#endif + +#if defined(RGB_MATRIX_ENABLE) +bool rgb_matrix_indicators_lemokey(void) { +# ifdef LK_WIRELESS_ENABLE + extern bool rgb_matrix_indicators_bt(void); + rgb_matrix_indicators_bt(); +# endif +# ifdef FACTORY_TEST_ENABLE + factory_test_indicator(); +# endif + return true; +} +#endif + +__attribute__((weak)) bool lemokey_task_kb(void) { + return true; +} + +void lemokey_task(void) { +#ifdef LK_WIRELESS_ENABLE + extern void wireless_tasks(void); + wireless_tasks(); +#endif +#ifdef FACTORY_TEST_ENABLE + factory_test_task(); +#endif + lemokey_common_task(); + + lemokey_task_kb(); +} + +bool process_record_kb(uint16_t keycode, keyrecord_t *record) { + if (!process_record_user(keycode, record)) return false; + + if (!process_record_lemokey(keycode, record)) return false; + + return true; +} + +#ifdef RGB_MATRIX_ENABLE +bool rgb_matrix_indicators_kb(void) { + if (!rgb_matrix_indicators_user()) return false; + + rgb_matrix_indicators_lemokey(); + + return true; +} +#endif + +#ifdef LED_MATRIX_ENABLE +bool led_matrix_indicators_kb(void) { + if (!led_matrix_indicators_user()) return false; + + led_matrix_indicators_lemokey(); + + return true; +} +#endif + +void housekeeping_task_kb(void) { + lemokey_task(); +} diff --git a/keyboards/lemokey/common/lemokey_task.h b/keyboards/lemokey/common/lemokey_task.h new file mode 100644 index 00000000000..9230c5592c4 --- /dev/null +++ b/keyboards/lemokey/common/lemokey_task.h @@ -0,0 +1,24 @@ +/* Copyright 2022 @ Keychron (https://www.lemokey.com) + * + * 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 "stdint.h" +#include "action.h" + +bool lemokey_task_kb(void); +bool process_record_lemokey_kb(uint16_t keycode, keyrecord_t *record); +void lemokey_task(void); diff --git a/keyboards/lemokey/common/wireless/bat_level_animation.c b/keyboards/lemokey/common/wireless/bat_level_animation.c new file mode 100644 index 00000000000..2c63ec6cf75 --- /dev/null +++ b/keyboards/lemokey/common/wireless/bat_level_animation.c @@ -0,0 +1,147 @@ + +#include "quantum.h" +#include "wireless.h" +#include "indicator.h" +#include "lpm.h" +#if defined(PROTOCOL_CHIBIOS) +# include +#elif if defined(PROTOCOL_LUFA) +# include "lufa.h" +#endif +#include "eeprom.h" + +#if (defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE)) && defined(BAT_LEVEL_LED_LIST) + +#ifndef BAT_LEVEL_GROWING_INTERVAL +# define BAT_LEVEL_GROWING_INTERVAL 150 +#endif + +#ifndef BAT_LEVEL_ON_INTERVAL +# define BAT_LEVEL_ON_INTERVAL 3000 +#endif + +#ifdef LED_MATRIX_ENABLE +# define LED_DRIVER_IS_ENABLED led_matrix_is_enabled +#endif + +#ifdef RGB_MATRIX_ENABLE +# define LED_DRIVER_IS_ENABLED rgb_matrix_is_enabled +#endif + +enum { + BAT_LVL_ANI_NONE, + BAT_LVL_ANI_GROWING, + BAT_LVL_ANI_BLINK_OFF, + BAT_LVL_ANI_BLINK_ON, +}; + +static uint8_t animation_state = 0; +static uint32_t bat_lvl_ani_timer_buffer = 0; +static uint8_t bat_percentage; +static uint8_t cur_percentage; +static uint32_t time_interval; +#ifdef RGB_MATRIX_ENABLE +static uint8_t r, g, b; +#endif + +extern indicator_config_t indicator_config; +extern backlight_state_t original_backlight_state; + +void bat_level_animiation_start(uint8_t percentage) { + /* Turn on backlight mode for indicator */ + indicator_enable(); + + animation_state = BAT_LVL_ANI_GROWING; + bat_percentage = percentage; + bat_lvl_ani_timer_buffer = timer_read32(); + cur_percentage = 0; + time_interval = BAT_LEVEL_GROWING_INTERVAL; +#ifdef RGB_MATRIX_ENABLE + r = g = b = 255; +#endif +} + +void bat_level_animiation_stop(void) { + animation_state = BAT_LVL_ANI_NONE; +} + +bool bat_level_animiation_actived(void) { + return animation_state; +} + +void bat_level_animiation_indicate(void) { +#ifdef LED_MATRIX_ENABLE + uint8_t bat_lvl_led_list[10] = BAT_LEVEL_LED_LIST; + + for (uint8_t i = 0; i <= LED_MATRIX_LED_COUNT; i++) { + led_matrix_set_value(i, 0); + } + + if (animation_state == BAT_LVL_ANI_GROWING || animation_state == BAT_LVL_ANI_BLINK_ON) + for (uint8_t i = 0; i < cur_percentage / 10; i++) + led_matrix_set_value(bat_lvl_led_list[i], 255); +#endif + +#ifdef RGB_MATRIX_ENABLE + uint8_t bat_lvl_led_list[10] = BAT_LEVEL_LED_LIST; + + for (uint8_t i = 0; i <= RGB_MATRIX_LED_COUNT; i++) { + rgb_matrix_set_color(i, 0, 0, 0); + } + + if (animation_state == BAT_LVL_ANI_GROWING || animation_state == BAT_LVL_ANI_BLINK_ON) { + for (uint8_t i = 0; i < cur_percentage / 10; i++) { + rgb_matrix_set_color(bat_lvl_led_list[i], r, g, b); + } + } +#endif +} + +void bat_level_animiation_update(void) { + switch (animation_state) { + case BAT_LVL_ANI_GROWING: + if (cur_percentage < bat_percentage) + cur_percentage += 10; + else { + if (cur_percentage == 0) cur_percentage = 10; + animation_state = BAT_LVL_ANI_BLINK_OFF; + } + break; + + case BAT_LVL_ANI_BLINK_OFF: +#ifdef RGB_MATRIX_ENABLE + if (bat_percentage < 30) { + r = 255; + b = g = 0; + } else { + r = b = 0; + g = 255; + } +#endif + time_interval = BAT_LEVEL_ON_INTERVAL; + animation_state = BAT_LVL_ANI_BLINK_ON; + break; + + case BAT_LVL_ANI_BLINK_ON: + animation_state = BAT_LVL_ANI_NONE; + indicator_eeconfig_reload(); + if (indicator_config.value == 0 && !LED_DRIVER_IS_ENABLED()) { + indicator_disable(); + } + lpm_timer_reset(); + break; + + default: + break; + } + + bat_lvl_ani_timer_buffer = timer_read32(); +} + +void bat_level_animiation_task(void) { + if (animation_state && sync_timer_elapsed32(bat_lvl_ani_timer_buffer) > time_interval) { + bat_level_animiation_update(); + } +} + +#endif diff --git a/keyboards/lemokey/common/wireless/bat_level_animation.h b/keyboards/lemokey/common/wireless/bat_level_animation.h new file mode 100644 index 00000000000..716e924103f --- /dev/null +++ b/keyboards/lemokey/common/wireless/bat_level_animation.h @@ -0,0 +1,23 @@ +/* Copyright 2022 @ lokher (https://www.keychron.com) + * + * 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 + +void bat_level_animiation_start(uint8_t percentage); +void bat_level_animiation_stop(void); +bool bat_level_animiation_actived(void); +void bat_level_animiation_indicate(void); +void bat_level_animiation_task(void); diff --git a/keyboards/lemokey/common/wireless/battery.c b/keyboards/lemokey/common/wireless/battery.c new file mode 100644 index 00000000000..36b537f006c --- /dev/null +++ b/keyboards/lemokey/common/wireless/battery.c @@ -0,0 +1,239 @@ +/* Copyright 2023 @ lokher (https://www.lemokey.com) + * + * 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" +#include "wireless.h" +#include "battery.h" +#include "transport.h" +#include "lkbt51.h" +#include "lpm.h" +#include "indicator.h" +#include "rtc_timer.h" +#include "analog.h" + +#define BATTERY_EMPTY_COUNT 10 +#define CRITICAL_LOW_COUNT 20 + +/* Battery voltage resistive voltage divider setting of MCU */ +#ifndef RVD_R1 +# define RVD_R1 10 // Upper side resitor value (uint: KΩ) +#endif +#ifndef RVD_R2 +# define RVD_R2 10 // Lower side resitor value (uint: KΩ) +#endif + +/* Battery voltage resistive voltage divider setting of Bluetooth */ +#ifndef LKBT51_RVD_R1 +# define LKBT51_RVD_R1 560 +#endif +#ifndef LKBT51_RVD_R2 +# define LKBT51_RVD_R2 499 +#endif + +#ifndef VOLTAGE_TRIM_LED_MATRIX +# define VOLTAGE_TRIM_LED_MATRIX 30 +#endif + +#ifndef VOLTAGE_TRIM_RGB_MATRIX +# define VOLTAGE_TRIM_RGB_MATRIX 60 +#endif + +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) +extern uint8_t g_pwm_buffer[DRIVER_COUNT][192]; +#endif + +static uint32_t bat_monitor_timer_buffer = 0; +static uint16_t voltage = FULL_VOLTAGE_VALUE; +static uint8_t bat_empty = 0; +static uint8_t critical_low = 0; +static uint8_t bat_state; +static uint8_t power_on_sample = 0; + +void battery_init(void) { + bat_state = BAT_NOT_CHARGING; +#if defined(BAT_CHARGING_PIN) +# if (BAT_CHARGING_LEVEL == 0) + palSetLineMode(BAT_CHARGING_PIN, PAL_MODE_INPUT_PULLUP); +# else + palSetLineMode(BAT_CHARGING_PIN, PAL_MODE_INPUT_PULLDOWN); +# endif +#endif + +#ifdef BAT_ADC_ENABLE_PIN + palSetLineMode(BAT_ADC_ENABLE_PIN, PAL_MODE_OUTPUT_PUSHPULL); + writePin(BAT_ADC_ENABLE_PIN, 1); +#endif +#ifdef BAT_ADC_PIN + palSetLineMode(BAT_ADC_PIN, PAL_MODE_INPUT_ANALOG); +#endif +} + +void battery_stop(void) { +#if (HAL_USE_ADC) +# ifdef BAT_ADC_ENABLE_PIN + writePin(BAT_ADC_ENABLE_PIN, 0); +# endif +# ifdef BAT_ADC_PIN + palSetLineMode(BAT_ADC_PIN, PAL_MODE_INPUT_ANALOG); + analog_stop(BAT_ADC_PIN); +# endif +#endif +} + +__attribute__((weak)) void battery_measure(void) { + lkbt51_read_state_reg(0x05, 0x02); +} + +/* Calculate the voltage */ +__attribute__((weak)) void battery_calculate_voltage(bool vol_src_bt, uint16_t value) { + uint16_t voltage; + + if (vol_src_bt) + voltage = ((uint32_t)value) * (LKBT51_RVD_R1 + LKBT51_RVD_R2) / LKBT51_RVD_R2; + else + voltage = (uint32_t)value * 3300 / 1024 * (RVD_R1 + RVD_R2) / RVD_R2; + +#ifdef LED_MATRIX_ENABLE + if (led_matrix_is_enabled()) { + uint32_t totalBuf = 0; + + for (uint8_t i = 0; i < DRIVER_COUNT; i++) + for (uint8_t j = 0; j < 192; j++) + totalBuf += g_pwm_buffer[i][j]; + /* We assumpt it is linear relationship*/ + voltage += (VOLTAGE_TRIM_LED_MATRIX * totalBuf / LED_MATRIX_LED_COUNT / 255); + } +#endif +#ifdef RGB_MATRIX_ENABLE + if (rgb_matrix_is_enabled()) { + uint32_t totalBuf = 0; + + for (uint8_t i = 0; i < DRIVER_COUNT; i++) + for (uint8_t j = 0; j < 192; j++) + totalBuf += g_pwm_buffer[i][j]; + /* We assumpt it is linear relationship*/ + uint32_t compensation = VOLTAGE_TRIM_RGB_MATRIX * totalBuf / RGB_MATRIX_LED_COUNT / 255 / 3; + + voltage += compensation; + } +#endif + + battery_set_voltage(voltage); +} + +void battery_set_voltage(uint16_t value) { + voltage = value; +} + +uint16_t battery_get_voltage(void) { + return voltage; +} + +uint8_t battery_get_percentage(void) { + if (voltage > FULL_VOLTAGE_VALUE) return 100; + + if (voltage > EMPTY_VOLTAGE_VALUE) { + return ((uint32_t)voltage - EMPTY_VOLTAGE_VALUE) * 80 / (FULL_VOLTAGE_VALUE - EMPTY_VOLTAGE_VALUE) + 20; + } + + if (voltage > SHUTDOWN_VOLTAGE_VALUE) { + return ((uint32_t)voltage - SHUTDOWN_VOLTAGE_VALUE) * 20 / (EMPTY_VOLTAGE_VALUE - SHUTDOWN_VOLTAGE_VALUE); + } else + return 0; +} + +bool battery_is_empty(void) { + return bat_empty > BATTERY_EMPTY_COUNT; +} + +bool battery_is_critical_low(void) { + return critical_low > CRITICAL_LOW_COUNT; +} + +void battery_check_empty(void) { + if (voltage < EMPTY_VOLTAGE_VALUE) { + if (bat_empty <= BATTERY_EMPTY_COUNT) { + if (++bat_empty > BATTERY_EMPTY_COUNT) { +#ifdef BAT_LOW_LED_PIN + indicator_battery_low_enable(true); +#endif +#if defined(LOW_BAT_IND_INDEX) + indicator_battery_low_backlit_enable(true); +#endif + power_on_sample = VOLTAGE_POWER_ON_MEASURE_COUNT; + } + } + } +} + +void battery_check_critical_low(void) { + if (voltage < SHUTDOWN_VOLTAGE_VALUE) { + if (critical_low <= CRITICAL_LOW_COUNT) { + if (++critical_low > CRITICAL_LOW_COUNT) wireless_low_battery_shutdown(); + } + } else if (critical_low <= CRITICAL_LOW_COUNT) { + critical_low = 0; + } +} + +bool battery_power_on_sample(void) { + return power_on_sample < VOLTAGE_POWER_ON_MEASURE_COUNT; +} + +void battery_task(void) { + uint32_t t = rtc_timer_elapsed_ms(bat_monitor_timer_buffer); + if ((get_transport() & TRANSPORT_WIRELESS) && (wireless_get_state() == WT_CONNECTED || battery_power_on_sample())) { +#if defined(BAT_CHARGING_PIN) + if (usb_power_connected() && t > VOLTAGE_MEASURE_INTERVAL) { + if (readPin(BAT_CHARGING_PIN) == BAT_CHARGING_LEVEL) + lkbt51_update_bat_state(BAT_CHARGING); + else + lkbt51_update_bat_state(BAT_FULL_CHARGED); + } +#endif + + if ((battery_power_on_sample() +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) + && !indicator_is_enabled() +#endif + && t > BACKLIGHT_OFF_VOLTAGE_MEASURE_INTERVAL) || + t > VOLTAGE_MEASURE_INTERVAL) { + + battery_check_empty(); + battery_check_critical_low(); + + bat_monitor_timer_buffer = rtc_timer_read_ms(); + if (bat_monitor_timer_buffer > RTC_MAX_TIME) { + bat_monitor_timer_buffer = 0; + rtc_timer_clear(); + } + + battery_measure(); + if (power_on_sample < VOLTAGE_POWER_ON_MEASURE_COUNT) power_on_sample++; + } + } + + if ((bat_empty || critical_low) && usb_power_connected()) { + bat_empty = false; + critical_low = false; +#ifdef BAT_LOW_LED_PIN + indicator_battery_low_enable(false); +#endif +#if defined(LOW_BAT_IND_INDEX) + indicator_battery_low_backlit_enable(false); +#endif + } +} diff --git a/keyboards/lemokey/common/wireless/battery.h b/keyboards/lemokey/common/wireless/battery.h new file mode 100644 index 00000000000..95ba4558b36 --- /dev/null +++ b/keyboards/lemokey/common/wireless/battery.h @@ -0,0 +1,62 @@ +/* Copyright 2023 @ lokher (https://www.lemokey.com) + * + * 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 + +enum { + BAT_NOT_CHARGING = 0, + BAT_CHARGING, + BAT_FULL_CHARGED, +}; + +#ifndef FULL_VOLTAGE_VALUE +# define FULL_VOLTAGE_VALUE 4100 +#endif + +#ifndef EMPTY_VOLTAGE_VALUE +# define EMPTY_VOLTAGE_VALUE 3500 +#endif + +#ifndef SHUTDOWN_VOLTAGE_VALUE +# define SHUTDOWN_VOLTAGE_VALUE 3300 +#endif + +#ifndef VOLTAGE_MEASURE_INTERVAL +# define VOLTAGE_MEASURE_INTERVAL 3000 +#endif + +#ifndef VOLTAGE_POWER_ON_MEASURE_COUNT +# define VOLTAGE_POWER_ON_MEASURE_COUNT 15 +#endif + +#ifndef BACKLIGHT_OFF_VOLTAGE_MEASURE_INTERVAL +# define BACKLIGHT_OFF_VOLTAGE_MEASURE_INTERVAL 200 +#endif + +void battery_init(void); +void battery_stop(void); + +void battery_measure(void); +void battery_calculate_voltage(bool vol_src_bt, uint16_t value); +void battery_set_voltage(uint16_t value); +uint16_t battery_get_voltage(void); +uint8_t battery_get_percentage(void); +void indicator_battery_low_enable(bool enable); +bool battery_is_empty(void); +bool battery_is_critical_low(void); +bool battery_power_on_sample(void); + +void battery_task(void); diff --git a/keyboards/lemokey/common/wireless/indicator.c b/keyboards/lemokey/common/wireless/indicator.c new file mode 100644 index 00000000000..b969f801792 --- /dev/null +++ b/keyboards/lemokey/common/wireless/indicator.c @@ -0,0 +1,720 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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" +#include "indicator.h" +#include "transport.h" +#include "battery.h" +#include "eeconfig.h" +#include "wireless_config.h" +#include "config.h" +#include "rtc_timer.h" +#include "lemokey_common.h" +#include "usb_main.h" +#ifdef FACTORY_TEST_ENABLE +# include "factory_test.h" +#endif +#include "lpm.h" + +#include "lemokey_task.h" +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) +# ifdef LED_MATRIX_ENABLE +# include "led_matrix.h" +# endif +# ifdef RGB_MATRIX_ENABLE +# include "rgb_matrix.h" +# endif +# include "bat_level_animation.h" +# include "eeprom.h" +#endif + +#define HOST_INDEX_MASK 0x0F +#define HOST_P2P4G 0x10 +#define LED_ON 0x80 + +// #define RGB_MATRIX_TIMEOUT_INFINITE 0xFFFFFFFF +#ifdef LED_MATRIX_ENABLE +# define DECIDE_TIME(t, duration) (duration == 0 ? LED_MATRIX_TIMEOUT_INFINITE : ((t > duration) ? t : duration)) +#endif +#ifdef RGB_MATRIX_ENABLE +# define DECIDE_TIME(t, duration) (duration == 0 ? RGB_MATRIX_TIMEOUT_INFINITE : ((t > duration) ? t : duration)) +#endif + +#define INDICATOR_SET(s) memcpy(&indicator_config, &s##_config, sizeof(indicator_config_t)); + +enum { + BACKLIGHT_OFF = 0x00, + BACKLIGHT_ON_CONNECTED = 0x01, + BACKLIGHT_ON_UNCONNECTED = 0x02, +}; + +static indicator_config_t pairing_config = INDICATOR_CONFIG_PARING; +static indicator_config_t connected_config = INDICATOR_CONFIG_CONNECTD; +static indicator_config_t reconnecting_config = INDICATOR_CONFIG_RECONNECTING; +static indicator_config_t disconnected_config = INDICATOR_CONFIG_DISCONNECTED; +indicator_config_t indicator_config; +static wt_state_t indicator_state; +static uint16_t next_period; +static indicator_type_t type; +static uint32_t indicator_timer_buffer = 0; + +#if defined(BAT_LOW_LED_PIN) +static uint32_t bat_low_pin_indicator = 0; +static uint32_t bat_low_blink_duration = 0; +#endif + +#if defined(LOW_BAT_IND_INDEX) +static uint32_t bat_low_backlit_indicator = 0; +static uint8_t bat_low_ind_state = 0; +static uint32_t rtc_time = 0; +#endif + +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) +backlight_state_t original_backlight_state; + +# ifdef BT_HOST_LED_MATRIX_LIST +static uint8_t bt_host_led_matrix_list[BT_HOST_DEVICES_COUNT] = BT_HOST_LED_MATRIX_LIST; +# endif + +# ifdef P2P4G_HOST_LED_MATRIX_LIST +static uint8_t p2p4g_host_led_matrix_list[P2P4G_HOST_DEVICES_COUNT] = P2P4G_HOST_LED_MATRIX_LIST; +# endif + +#endif + +#ifdef BT_HOST_LED_PIN_LIST +static pin_t bt_led_pin_list[BT_HOST_DEVICES_COUNT] = BT_HOST_LED_PIN_LIST; +#endif + +#ifdef P24G_HOST_LED_PIN_LIST +static pin_t p24g_led_pin_list[P24G_HOST_DEVICES_COUNT] = P24G_HOST_LED_PIN_LIST; +#endif + +#ifdef LED_MATRIX_ENABLE +# define LED_DRIVER led_matrix_driver +# define LED_INDICATORS_KB led_matrix_indicators_bt +# define LED_INDICATORS_USER led_matrix_indicators_user +# define LED_NONE_INDICATORS_KB led_matrix_none_indicators_kb +# define SET_ALL_LED_OFF() led_matrix_set_value_all(0) +# define SET_LED_OFF(idx) led_matrix_set_value(idx, 0) +# define SET_LED_ON(idx) led_matrix_set_value(idx, 255) +# define SET_LED_BT(idx) led_matrix_set_value(idx, 255) +# define SET_LED_P24G(idx) led_matrix_set_value(idx, 255) +# define SET_LED_LOW_BAT(idx) led_matrix_set_value(idx, 255) +# define LED_DRIVER_IS_ENABLED led_matrix_is_enabled +# define LED_DRIVER_EECONFIG_RELOAD() \ + eeprom_read_block(&led_matrix_eeconfig, EECONFIG_LED_MATRIX, sizeof(led_matrix_eeconfig)); \ + if (!led_matrix_eeconfig.mode) { \ + eeconfig_update_led_matrix_default(); \ + } +# define LED_DRIVER_ALLOW_SHUTDOWN led_matrix_driver_allow_shutdown +# define LED_DRIVER_SHUTDOWN led_matrix_driver_shutdown +# define LED_DRIVER_EXIT_SHUTDOWN led_matrix_driver_exit_shutdown +# define LED_DRIVER_ENABLE_NOEEPROM led_matrix_enable_noeeprom +# define LED_DRIVER_DISABLE_NOEEPROM led_matrix_disable_noeeprom +# define LED_DRIVER_DISABLE_TIMEOUT_SET led_matrix_disable_timeout_set +# define LED_DRIVER_DISABLE_TIME_RESET led_matrix_disable_time_reset +# define LED_DRIVER_TIMEOUTED led_matrix_timeouted +#endif + +#ifdef RGB_MATRIX_ENABLE +# define LED_DRIVER rgb_matrix_driver +# define LED_INDICATORS_KB rgb_matrix_indicators_bt +# define LED_INDICATORS_USER rgb_matrix_indicators_user +# define LED_NONE_INDICATORS_KB rgb_matrix_none_indicators_kb +# define SET_ALL_LED_OFF() rgb_matrix_set_color_all(0, 0, 0) +# define SET_LED_OFF(idx) rgb_matrix_set_color(idx, 0, 0, 0) +# define SET_LED_ON(idx) rgb_matrix_set_color(idx, 255, 255, 255) +# define SET_LED_BT(idx) rgb_matrix_set_color(idx, 0, 0, 255) +# define SET_LED_P24G(idx) rgb_matrix_set_color(idx, 0, 255, 0) +# define SET_LED_LOW_BAT(idx) rgb_matrix_set_color(idx, 255, 0, 0) +# define LED_DRIVER_IS_ENABLED rgb_matrix_is_enabled +# define LED_DRIVER_EECONFIG_RELOAD() \ + eeprom_read_block(&rgb_matrix_config, EECONFIG_RGB_MATRIX, sizeof(rgb_matrix_config)); \ + if (!rgb_matrix_config.mode) { \ + eeconfig_update_rgb_matrix_default(); \ + } +# define LED_DRIVER_ALLOW_SHUTDOWN rgb_matrix_driver_allow_shutdown +# define LED_DRIVER_SHUTDOWN rgb_matrix_driver_shutdown +# define LED_DRIVER_EXIT_SHUTDOWN rgb_matrix_driver_exit_shutdown +# define LED_DRIVER_ENABLE_NOEEPROM rgb_matrix_enable_noeeprom +# define LED_DRIVER_DISABLE_NOEEPROM rgb_matrix_disable_noeeprom +# define LED_DRIVER_DISABLE_TIMEOUT_SET rgb_matrix_disable_timeout_set +# define LED_DRIVER_DISABLE_TIME_RESET rgb_matrix_disable_time_reset +# define LED_DRIVER_TIMEOUTED rgb_matrix_timeouted +#endif + +bool LED_INDICATORS_KB(void); + +void indicator_init(void) { + memset(&indicator_config, 0, sizeof(indicator_config)); + +#ifdef BT_HOST_LED_PIN_LIST + for (uint8_t i = 0; i < BT_HOST_DEVICES_COUNT; i++) { + setPinOutput(bt_led_pin_list[i]); + writePin(bt_led_pin_list[i], !HOST_LED_PIN_ON_STATE); + } +#endif + +#ifdef P24G_HOST_LED_PIN_LIST + for (uint8_t i = 0; i < P24G_HOST_DEVICES_COUNT; i++) { + setPinOutput(p24g_led_pin_list[i]); + writePin(p24g_led_pin_list[i], !HOST_LED_PIN_ON_STATE); + } +#endif + +#ifdef BAT_LOW_LED_PIN + setPinOutput(BAT_LOW_LED_PIN); + writePin(BAT_LOW_LED_PIN, !BAT_LOW_LED_PIN_ON_STATE); +#endif +} + +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) +void indicator_enable(void) { + if (!LED_DRIVER_IS_ENABLED()) { + LED_DRIVER_ENABLE_NOEEPROM(); + } +} + +inline void indicator_disable(void) { + LED_DRIVER_DISABLE_NOEEPROM(); +} + +void indicator_set_backlit_timeout(uint32_t time) { + LED_DRIVER_DISABLE_TIMEOUT_SET(time); +} + +static inline void indicator_reset_backlit_time(void) { + LED_DRIVER_DISABLE_TIME_RESET(); +} + +bool indicator_is_enabled(void) { + return LED_DRIVER_IS_ENABLED(); +} + +void indicator_eeconfig_reload(void) { + LED_DRIVER_EECONFIG_RELOAD(); +} + +#endif + +bool indicator_is_running(void) { + return +#if defined(BAT_LOW_LED_PIN) + bat_low_blink_duration || +#endif +#if defined(LOW_BAT_IND_INDEX) + bat_low_ind_state || +#endif + !!indicator_config.value; +} + +static void indicator_timer_cb(void *arg) { + if (*(indicator_type_t *)arg != INDICATOR_LAST) type = *(indicator_type_t *)arg; + + bool time_up = false; + switch (type) { + case INDICATOR_NONE: + break; + case INDICATOR_OFF: + next_period = 0; + time_up = true; + break; + + case INDICATOR_ON: + if (indicator_config.value) { + if (indicator_config.elapsed == 0) { + indicator_config.value |= LED_ON; + + if (indicator_config.duration) { + indicator_config.elapsed += indicator_config.duration; + } + } else + time_up = true; + } + break; + + case INDICATOR_ON_OFF: + if (indicator_config.value) { + if (indicator_config.elapsed == 0) { + indicator_config.value |= LED_ON; + next_period = indicator_config.on_time; + } else { + indicator_config.value = indicator_config.value & 0x1F; + next_period = indicator_config.duration - indicator_config.on_time; + } + + if ((indicator_config.duration == 0 || indicator_config.elapsed <= indicator_config.duration) && next_period != 0) { + indicator_config.elapsed += next_period; + } else { + time_up = true; + } + } + break; + + case INDICATOR_BLINK: + if (indicator_config.value) { + if (indicator_config.value & LED_ON) { + indicator_config.value = indicator_config.value & 0x1F; + next_period = indicator_config.off_time; + } else { + indicator_config.value |= LED_ON; + next_period = indicator_config.on_time; + } + + if ((indicator_config.duration == 0 || indicator_config.elapsed <= indicator_config.duration) && next_period != 0) { + indicator_config.elapsed += next_period; + } else { + time_up = true; + } + } + break; + default: + time_up = true; + + next_period = 0; + break; + } + +#if defined(BT_HOST_LED_PIN_LIST) || defined(P24G_HOST_LED_PIN_LIST) + if (indicator_config.value) { + uint8_t idx = (indicator_config.value & HOST_INDEX_MASK) - 1; + + pin_t *led_lin_list = NULL; + uint8_t led_count; +# if defined(P24G_HOST_LED_PIN_LIST) + if (indicator_config.value & HOST_P2P4G) { + if (idx < P24G_HOST_DEVICES_COUNT) led_lin_list = p24g_led_pin_list; + led_count = P24G_HOST_DEVICES_COUNT; + } else +# endif + { + if (idx < BT_HOST_DEVICES_COUNT) led_lin_list = bt_led_pin_list; + led_count = BT_HOST_DEVICES_COUNT; + } + + for (uint8_t i = 0; i < led_count; i++) { + if (i != idx) writePin(led_lin_list[idx], !HOST_LED_PIN_ON_STATE); + } + + if (led_lin_list) { + if ((indicator_config.value & LED_ON) && !time_up) { + writePin(led_lin_list[idx], HOST_LED_PIN_ON_STATE); + } else { + writePin(led_lin_list[idx], !HOST_LED_PIN_ON_STATE); + } + } + } +#endif + + if (time_up) { + /* Set indicator to off on timeup, avoid keeping light up until next update in raindrop effect */ + indicator_config.value = indicator_config.value & 0x1F; +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) + LED_INDICATORS_KB(); +#endif + + indicator_config.value = 0; + lpm_timer_reset(); + } + + if (indicator_config.value == 0) { +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) + indicator_eeconfig_reload(); + if (!LED_DRIVER_IS_ENABLED()) indicator_disable(); +#endif + } +} + +void indicator_set(wt_state_t state, uint8_t host_index) { + if (get_transport() == TRANSPORT_USB) return; + + static uint8_t current_state = 0; + static uint8_t current_host = 0; + bool host_index_changed = false; + + if (host_index == 24) host_index = HOST_P2P4G | 0x01; + + if (current_host != host_index && state != WT_DISCONNECTED) { + host_index_changed = true; + current_host = host_index; + } + + if (current_state != state || host_index_changed || state == WT_RECONNECTING) { + current_state = state; + } else { + return; + } + + indicator_timer_buffer = timer_read32(); + +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) + /* Turn on backlight mode for indicator */ + indicator_enable(); + indicator_reset_backlit_time(); +#endif + + switch (state) { + case WT_DISCONNECTED: + +#if defined(BT_HOST_LED_PIN_LIST) + if ((host_index & HOST_P2P4G) != HOST_P2P4G) writePin(bt_led_pin_list[(host_index & HOST_INDEX_MASK) - 1], !HOST_LED_PIN_ON_STATE); +#endif +#if defined(P24G_HOST_LED_PIN_LIST) + if (host_index & HOST_P2P4G) writePin(p24g_led_pin_list[(host_index & HOST_INDEX_MASK) - 1], !HOST_LED_PIN_ON_STATE); +#endif + + INDICATOR_SET(disconnected); + indicator_config.value = (indicator_config.type == INDICATOR_NONE) ? 0 : host_index; + indicator_timer_cb((void *)&indicator_config.type); +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) + if (battery_is_critical_low()) { + indicator_set_backlit_timeout(1000); + + } else { + /* Set timer so that user has chance to turn on the backlight when is off */ + indicator_set_backlit_timeout(DECIDE_TIME(DISCONNECTED_BACKLIGHT_DISABLE_TIMEOUT * 1000, indicator_config.duration)); + } +#endif + break; + + case WT_CONNECTED: + if (indicator_state != WT_CONNECTED) { + INDICATOR_SET(connected); + indicator_config.value = (indicator_config.type == INDICATOR_NONE) ? 0 : host_index; + indicator_timer_cb((void *)&indicator_config.type); + } +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) + indicator_set_backlit_timeout(DECIDE_TIME(CONNECTED_BACKLIGHT_DISABLE_TIMEOUT * 1000, indicator_config.duration)); +#endif + break; + + case WT_PARING: + INDICATOR_SET(pairing); + indicator_config.value = (indicator_config.type == INDICATOR_NONE) ? 0 : LED_ON | host_index; + indicator_timer_cb((void *)&indicator_config.type); +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) + indicator_set_backlit_timeout(DECIDE_TIME(DISCONNECTED_BACKLIGHT_DISABLE_TIMEOUT * 1000, indicator_config.duration)); +#endif + break; + + case WT_RECONNECTING: + INDICATOR_SET(reconnecting); + indicator_config.value = (indicator_config.type == INDICATOR_NONE) ? 0 : LED_ON | host_index; + indicator_timer_cb((void *)&indicator_config.type); +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) + indicator_set_backlit_timeout(DECIDE_TIME(DISCONNECTED_BACKLIGHT_DISABLE_TIMEOUT * 1000, indicator_config.duration)); +#endif + break; + + case WT_SUSPEND: + INDICATOR_SET(disconnected); + indicator_config.value = (indicator_config.type == INDICATOR_NONE) ? 0 : host_index; + indicator_timer_cb((void *)&indicator_config.type); +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) +# ifdef FACTORY_TEST_ENABLE + if (factory_reset_indicating()) + indicator_set_backlit_timeout(3000); + else +# endif + { + indicator_set_backlit_timeout(1000); + } +#endif + +#if defined(BT_HOST_LED_PIN_LIST) + for (uint8_t i = 0; i < BT_HOST_DEVICES_COUNT; i++) + writePin(bt_led_pin_list[i], !HOST_LED_PIN_ON_STATE); +#endif +#if defined(P24G_HOST_LED_PIN_LIST) + for (uint8_t i = 0; i < P24G_HOST_DEVICES_COUNT; i++) + writePin(p24g_led_pin_list[i], !HOST_LED_PIN_ON_STATE); +#endif + break; + + default: + break; + } + + indicator_state = state; +} + +void indicator_stop(void) { + indicator_config.value = 0; +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) + indicator_eeconfig_reload(); + + if (indicator_is_enabled()) { + indicator_enable(); + } else { + indicator_disable(); + } +#endif +} + +#ifdef BAT_LOW_LED_PIN +void indicator_battery_low_enable(bool enable) { + if (enable) { + if (bat_low_blink_duration == 0) { + bat_low_blink_duration = bat_low_pin_indicator = timer_read32(); + } else + bat_low_blink_duration = timer_read32(); + } else + writePin(BAT_LOW_LED_PIN, !BAT_LOW_LED_PIN_ON_STATE); +} +#endif + +#if defined(LOW_BAT_IND_INDEX) +void indicator_battery_low_backlit_enable(bool enable) { + if (enable) { + uint32_t t = rtc_timer_read_ms(); + /* Check overflow */ + if (rtc_time > t) { + if (bat_low_ind_state == 0) + rtc_time = t; // Update rtc_time if indicating is not running + else { + rtc_time += t; + } + } + /* Indicating at first time or after the interval */ + if ((rtc_time == 0 || t - rtc_time > LOW_BAT_LED_TRIG_INTERVAL) && bat_low_ind_state == 0) { + bat_low_backlit_indicator = enable ? timer_read32() : 0; + rtc_time = rtc_timer_read_ms(); + bat_low_ind_state = 1; + + indicator_enable(); + } + } else { + rtc_time = 0; + bat_low_ind_state = 0; + + indicator_eeconfig_reload(); + if (!LED_DRIVER_IS_ENABLED()) indicator_disable(); + } +} +#endif + +void indicator_battery_low(void) { +#ifdef BAT_LOW_LED_PIN + if (bat_low_pin_indicator && timer_elapsed32(bat_low_pin_indicator) > (LOW_BAT_LED_BLINK_PERIOD)) { + togglePin(BAT_LOW_LED_PIN); + bat_low_pin_indicator = timer_read32(); + // Turn off low battery indication if we reach the duration + if (timer_elapsed32(bat_low_blink_duration) > LOW_BAT_LED_BLINK_DURATION && palReadLine(BAT_LOW_LED_PIN) != BAT_LOW_LED_PIN_ON_STATE) { + bat_low_blink_duration = bat_low_pin_indicator = 0; + } + } +#endif +#if defined(LOW_BAT_IND_INDEX) + if (bat_low_ind_state) { + if ((bat_low_ind_state & 0x0F) <= (LOW_BAT_LED_BLINK_TIMES) && timer_elapsed32(bat_low_backlit_indicator) > (LOW_BAT_LED_BLINK_PERIOD)) { + if (bat_low_ind_state & 0x80) { + bat_low_ind_state &= 0x7F; + bat_low_ind_state++; + } else { + bat_low_ind_state |= 0x80; + } + + bat_low_backlit_indicator = timer_read32(); + + /* Restore backligth state */ + if ((bat_low_ind_state & 0x0F) > (LOW_BAT_LED_BLINK_TIMES)) { +# if defined(NUM_LOCK_INDEX) || defined(CAPS_LOCK_INDEX) || defined(SCROLL_LOCK_INDEX) || defined(COMPOSE_LOCK_INDEX) || defined(KANA_LOCK_INDEX) + if (LED_DRIVER_ALLOW_SHUTDOWN()) +# endif + indicator_disable(); + } + } else if ((bat_low_ind_state & 0x0F) > (LOW_BAT_LED_BLINK_TIMES)) { + bat_low_ind_state = 0; + lpm_timer_reset(); + } + } +#endif +} + +void indicator_task(void) { +#if (defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE)) && defined(BAT_LEVEL_LED_LIST) + bat_level_animiation_task(); +#endif + if (indicator_config.value && timer_elapsed32(indicator_timer_buffer) >= next_period) { + indicator_timer_cb((void *)&type); + indicator_timer_buffer = timer_read32(); + } + + indicator_battery_low(); +} + +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) +__attribute__((weak)) void os_state_indicate(void) { +# if defined(RGB_DISABLE_WHEN_USB_SUSPENDED) || defined(LED_DISABLE_WHEN_USB_SUSPENDED) + if (get_transport() == TRANSPORT_USB && USB_DRIVER.state == USB_SUSPENDED) return; +# endif + +# if defined(NUM_LOCK_INDEX) + if (host_keyboard_led_state().num_lock) { + SET_LED_ON(NUM_LOCK_INDEX); + } +# endif +# if defined(CAPS_LOCK_INDEX) + if (host_keyboard_led_state().caps_lock) { +# if defined(DIM_CAPS_LOCK) + SET_LED_OFF(CAPS_LOCK_INDEX); +# else + SET_LED_ON(CAPS_LOCK_INDEX); +# endif + } +# endif +# if defined(SCROLL_LOCK_INDEX) + if (host_keyboard_led_state().scroll_lock) { + SET_LED_ON(SCROLL_LOCK_INDEX); + } +# endif +# if defined(COMPOSE_LOCK_INDEX) + if (host_keyboard_led_state().compose) { + SET_LED_ON(COMPOSE_LOCK_INDEX); + } +# endif +# if defined(KANA_LOCK_INDEX) + if (host_keyboard_led_state().kana) { + SET_LED_ON(KANA_LOCK_INDEX); + } +# endif +} + +bool LED_INDICATORS_KB(void) { + if (get_transport() & TRANSPORT_WIRELESS) { + /* Prevent backlight flash caused by key activities */ + if (battery_is_critical_low()) { + SET_ALL_LED_OFF(); + return true; + } + +# if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) + if (battery_is_empty()) SET_ALL_LED_OFF(); +# if defined(LOW_BAT_IND_INDEX) + if (bat_low_ind_state && (bat_low_ind_state & 0x0F) <= LOW_BAT_LED_BLINK_TIMES) { + uint8_t idx_list[] = LOW_BAT_IND_INDEX; + for (uint8_t i = 0; i < sizeof(idx_list); i++) { + if (bat_low_ind_state & LED_ON) { + SET_LED_LOW_BAT(idx_list[i]); + } else { + SET_LED_OFF(idx_list[i]); + } + } + } +# endif +# endif +# if (defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE)) && defined(BAT_LEVEL_LED_LIST) + if (bat_level_animiation_actived()) { + bat_level_animiation_indicate(); + } +# endif + static uint8_t last_host_index = 0xFF; + + if (indicator_config.value) { + uint8_t host_index = indicator_config.value & HOST_INDEX_MASK; + + if (indicator_config.highlight) { + SET_ALL_LED_OFF(); + } else if (last_host_index != host_index) { + if (indicator_config.value & HOST_P2P4G) + SET_LED_OFF(p2p4g_host_led_matrix_list[host_index - 1]); + else + SET_LED_OFF(bt_host_led_matrix_list[host_index - 1]); + last_host_index = host_index; + } + + if (indicator_config.value & LED_ON) { +# ifdef P2P4G_HOST_LED_MATRIX_LIST + if (indicator_config.value & HOST_P2P4G) + SET_LED_P24G(p2p4g_host_led_matrix_list[host_index - 1]); + else +# endif + SET_LED_BT(bt_host_led_matrix_list[host_index - 1]); + + } else { +# ifdef P2P4G_HOST_LED_MATRIX_LIST + if (indicator_config.value & HOST_P2P4G) + SET_LED_OFF(p2p4g_host_led_matrix_list[host_index - 1]); + else +# endif + SET_LED_OFF(bt_host_led_matrix_list[host_index - 1]); + } + } else + os_state_indicate(); + + } else + os_state_indicate(); + + if (!LED_INDICATORS_USER()) return true; + + return true; +} + +bool led_update_kb(led_t led_state) { + bool res = led_update_user(led_state); + if (res) { + led_update_ports(led_state); + + if (!LED_DRIVER_IS_ENABLED() || (LED_DRIVER_IS_ENABLED() && LED_DRIVER_TIMEOUTED())) { +# if defined(LED_MATRIX_DRIVER_SHUTDOWN_ENABLE) || defined(RGB_MATRIX_DRIVER_SHUTDOWN_ENABLE) + LED_DRIVER_EXIT_SHUTDOWN(); +# endif + SET_ALL_LED_OFF(); + os_state_indicate(); + LED_DRIVER.flush(); +# if defined(LED_MATRIX_DRIVER_SHUTDOWN_ENABLE) || defined(RGB_MATRIX_DRIVER_SHUTDOWN_ENABLE) + if (LED_DRIVER_ALLOW_SHUTDOWN()) LED_DRIVER_SHUTDOWN(); +# endif + } + } + + return res; +} + +void LED_NONE_INDICATORS_KB(void) { +# if defined(RGB_DISABLE_WHEN_USB_SUSPENDED) + if (get_transport() == TRANSPORT_USB && USB_DRIVER.state == USB_SUSPENDED) return; +# endif +# if defined(LED_DISABLE_WHEN_USB_SUSPENDED) + if (get_transport() == TRANSPORT_USB && USB_DRIVER.state == USB_SUSPENDED) return; +# endif + + os_state_indicate(); +} + +# if defined(LED_MATRIX_DRIVER_SHUTDOWN_ENABLE) || defined(RGB_MATRIX_DRIVER_SHUTDOWN_ENABLE) +bool LED_DRIVER_ALLOW_SHUTDOWN(void) { +# if defined(NUM_LOCK_INDEX) + if (host_keyboard_led_state().num_lock) return false; +# endif +# if defined(CAPS_LOCK_INDEX) && !defined(DIM_CAPS_LOCK) + if (host_keyboard_led_state().caps_lock) return false; +# endif +# if defined(SCROLL_LOCK_INDEX) + if (host_keyboard_led_state().scroll_lock) return false; +# endif +# if defined(COMPOSE_LOCK_INDEX) + if (host_keyboard_led_state().compose) return false; +# endif +# if defined(KANA_LOCK_INDEX) + if (host_keyboard_led_state().kana) return false; +# endif + return true; +} +# endif + +#endif diff --git a/keyboards/lemokey/common/wireless/indicator.h b/keyboards/lemokey/common/wireless/indicator.h new file mode 100644 index 00000000000..f731d9ee0d2 --- /dev/null +++ b/keyboards/lemokey/common/wireless/indicator.h @@ -0,0 +1,117 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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 "config.h" +#include "wireless.h" + +/* Indication of pairing */ +#ifndef INDICATOR_CONFIG_PARING +# define INDICATOR_CONFIG_PARING {INDICATOR_BLINK, 1000, 1000, 0, true, 0}; +#endif + +/* Indication on Connected */ +#ifndef INDICATOR_CONFIG_CONNECTD +# define INDICATOR_CONFIG_CONNECTD {INDICATOR_ON_OFF, 2000, 250, 2000, true, 0}; +#endif + +/* Reconnecting indication */ +#ifndef INDICATOR_CONFIG_RECONNECTING +# define INDICATOR_CONFIG_RECONNECTING {INDICATOR_BLINK, 100, 100, 600, true, 0}; +#endif + +/* Disconnected indication */ +#ifndef INDICATOR_CONFIG_DISCONNECTED +# define INDICATOR_CONFIG_DISCONNECTED {INDICATOR_NONE, 100, 100, 600, false, 0}; +#endif + +/* Uint: Second */ +#ifndef DISCONNECTED_BACKLIGHT_DISABLE_TIMEOUT +# define DISCONNECTED_BACKLIGHT_OFF_DELAY_TIME 40 +#endif + +/* Uint: Second, the timer restarts on key activities. */ +#ifndef CONNECTED_BACKLIGHT_DISABLE_TIMEOUT +# define CONNECTED_BACKLIGHT_OFF_DELAY_TIME 600 +#endif + +#ifdef BAT_LOW_LED_PIN +/* Uint: ms */ +# ifndef LOW_BAT_LED_BLINK_PERIOD +# define LOW_BAT_LED_BLINK_PERIOD 1000 +# endif + +# ifndef LOW_BAT_LED_BLINK_DURATION +# define LOW_BAT_LED_BLINK_DURATION 10000 +# endif +#endif + +#ifdef LOW_BAT_IND_INDEX +/* Uint: ms */ +# ifndef LOW_BAT_LED_BLINK_PERIOD +# define LOW_BAT_LED_BLINK_PERIOD 500 +# endif + +# ifndef LOW_BAT_LED_BLINK_TIMES +# define LOW_BAT_LED_BLINK_TIMES 3 +# endif + +# ifndef LOW_BAT_LED_TRIG_INTERVAL +# define LOW_BAT_LED_TRIG_INTERVAL 30000 +# endif +#endif + +#if BT_HOST_MAX_COUNT > 6 +# pragma error("HOST_COUNT max value is 6") +#endif + +typedef enum { INDICATOR_NONE, INDICATOR_OFF, INDICATOR_ON, INDICATOR_ON_OFF, INDICATOR_BLINK, INDICATOR_LAST } indicator_type_t; + +typedef struct PACKED { + indicator_type_t type; + uint32_t on_time; + uint32_t off_time; + uint32_t duration; + bool highlight; + uint8_t value; + uint32_t elapsed; +} indicator_config_t; + +typedef struct PACKED { + uint8_t value; + bool saved; +} backlight_state_t; + +void indicator_init(void); +void indicator_set(wt_state_t state, uint8_t host_index); +void indicator_backlight_timer_reset(bool enable); +bool indicator_hook_key(uint16_t keycode); +void indicator_enable(void); +void indicator_disable(void); +void indicator_stop(void); +void indicator_eeconfig_reload(void); +bool indicator_is_enabled(void); +bool indicator_is_running(void); + +#ifdef BAT_LOW_LED_PIN +void indicator_battery_low_enable(bool enable); +#endif +#if defined(LOW_BAT_IND_INDEX) +void indicator_battery_low_backlit_enable(bool enable); +#endif + +void indicator_task(void); diff --git a/keyboards/lemokey/common/wireless/lemokey_wireless_common.c b/keyboards/lemokey/common/wireless/lemokey_wireless_common.c new file mode 100644 index 00000000000..e24c5e1693f --- /dev/null +++ b/keyboards/lemokey/common/wireless/lemokey_wireless_common.c @@ -0,0 +1,155 @@ +/* Copyright 2022 @ Keychron (https://www.keychron.com) + * + * 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 +#ifdef LK_WIRELESS_ENABLE +# include "lkbt51.h" +# include "wireless.h" +# include "indicator.h" +# include "transport.h" +# include "battery.h" +# include "bat_level_animation.h" +# include "lpm.h" +# include "lemokey_wireless_common.h" +# include "lemokey_task.h" +#endif +#include "lemokey_common.h" + +bool firstDisconnect = true; + +static uint32_t pairing_key_timer; +static uint8_t host_idx = 0; + +bool process_record_lemokey_wireless(uint16_t keycode, keyrecord_t *record) { + static uint8_t host_idx; + + switch (keycode) { + case BT_HST1 ... BT_HST3: + if (get_transport() == TRANSPORT_BLUETOOTH) { + if (record->event.pressed) { + host_idx = keycode - BT_HST1 + 1; + + pairing_key_timer = timer_read32(); + wireless_connect_ex(host_idx, 0); + } else { + host_idx = 0; + pairing_key_timer = 0; + } + } + break; + case P2P4G: + if (get_transport() == TRANSPORT_P2P4) { + if (record->event.pressed) { + host_idx = P24G_INDEX; + + pairing_key_timer = timer_read32(); + } else { + host_idx = 0; + pairing_key_timer = 0; + } + } + break; +#if (defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE)) && defined(BAT_LEVEL_LED_LIST) + case BAT_LVL: + if ((get_transport() & TRANSPORT_WIRELESS) && !usb_power_connected()) { + bat_level_animiation_start(battery_get_percentage()); + } + break; +#endif + + default: + break; + } + + return true; +} + +void lkbt51_param_init(void) { + /* Set bluetooth device name */ + lkbt51_set_local_name(PRODUCT); + wait_ms(3); + // clang-format off + /* Set bluetooth parameters */ + module_param_t param = {.event_mode = 0x02, + .connected_idle_timeout = 7200, + .pairing_timeout = 180, + .pairing_mode = 0, + .reconnect_timeout = 5, + .report_rate = 90, + .vendor_id_source = 1, + .verndor_id = 0x362D, + .product_id = PRODUCT_ID}; + // clang-format on + lkbt51_set_param(¶m); +} + +void wireless_enter_reset_kb(uint8_t reason) { + lkbt51_param_init(); +} + +void wireless_enter_disconnected_kb(uint8_t host_idx) { + /* CKBT51 bluetooth module boot time is slower, it enters disconnected after boot, + so we place initialization here. */ + if (firstDisconnect && timer_read32() < 1000) { + lkbt51_param_init(); + if (get_transport() == TRANSPORT_BLUETOOTH) wireless_connect(); + firstDisconnect = false; + } +} + +void lemokey_wireless_common_task(void) { + if (pairing_key_timer) { + if (timer_elapsed32(pairing_key_timer) > 2000) { + pairing_key_timer = 0; + wireless_pairing_ex(host_idx, NULL); + } + } +} + +void wireless_pre_task(void) { + static uint8_t mode = 0; + static uint32_t time = 0; + + if (time == 0) { + if ((readPin(BT_MODE_SELECT_PIN) << 1 | readPin(P2P4_MODE_SELECT_PIN)) != mode) { + mode = readPin(BT_MODE_SELECT_PIN) << 1 | readPin(P2P4_MODE_SELECT_PIN); + time = timer_read32(); + } + } + + if ((time && timer_elapsed32(time) > 100) || get_transport() == TRANSPORT_NONE) { + if ((readPin(BT_MODE_SELECT_PIN) << 1 | readPin(P2P4_MODE_SELECT_PIN)) == mode) { + time = 0; + + switch (mode) { + case 0x01: + set_transport(TRANSPORT_BLUETOOTH); + break; + case 0x02: + set_transport(TRANSPORT_P2P4); + break; + case 0x03: + set_transport(TRANSPORT_USB); + break; + default: + break; + } + } else { + mode = readPin(BT_MODE_SELECT_PIN) << 1 | readPin(P2P4_MODE_SELECT_PIN); + time = timer_read32(); + } + } +} diff --git a/keyboards/lemokey/common/wireless/lemokey_wireless_common.h b/keyboards/lemokey/common/wireless/lemokey_wireless_common.h new file mode 100644 index 00000000000..05b51406f72 --- /dev/null +++ b/keyboards/lemokey/common/wireless/lemokey_wireless_common.h @@ -0,0 +1,26 @@ +/* Copyright 2023 @ Keychron (https://www.lemokey.com) + * + * 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 "stdint.h" +#ifdef VIA_ENABLE +# include "via.h" +#endif +#include "quantum_keycodes.h" + +void lkbt51_param_init(void); + +bool process_record_lemokey_wireless(uint16_t keycode, keyrecord_t *record); +void lemokey_wireless_common_task(void); diff --git a/keyboards/lemokey/common/wireless/lkbt51.c b/keyboards/lemokey/common/wireless/lkbt51.c new file mode 100644 index 00000000000..56db438727e --- /dev/null +++ b/keyboards/lemokey/common/wireless/lkbt51.c @@ -0,0 +1,867 @@ +/* Copyright 2021 @ Keychron (https://www.keychron.com) + * + * 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" +#include "lkbt51.h" +#include "wireless.h" +#include "wireless_event_type.h" +#include "battery.h" +#include "raw_hid.h" +#include "report_buffer.h" +#include "factory_test.h" + +extern void factory_test_send(uint8_t* payload, uint8_t length); + +# ifndef RAW_EPSIZE +# define RAW_EPSIZE 32 +# endif + +#ifndef SPI_SCK_PIN +# define SPI_SCK_PIN A5 +#endif +#ifndef SPI_MISO_PIN +# define SPI_MISO_PIN A6 +#endif +#ifndef SPI_MOSI_PIN +# define SPI_MOSI_PIN A7 +#endif + +#ifndef SPI_CLK_PAL_MODE +# define SPI_CLK_PAL_MODE 5 +#endif +#ifndef SPI_MISO_PAL_MODE +# define SPI_MISO_PAL_MODE 5 +#endif +#ifndef SPI_MOSI_PAL_MODE +# define SPI_MOSI_PAL_MODE 5 +#endif + +#ifndef LKBT51_INT_INPUT_PIN +# error "LKBT51_INT_INPUT_PIN is not defined" +#endif + +#ifndef LKBT51_TX_RETRY_COUNT +# define LKBT51_TX_RETRY_COUNT 3 +#endif + +// clang-format off +enum { + /* HID Report */ + LKBT51_CMD_SEND_KB = 0x11, + LKBT51_CMD_SEND_KB_NKRO = 0x12, + LKBT51_CMD_SEND_CONSUMER = 0x13, + LKBT51_CMD_SEND_SYSTEM = 0x14, + LKBT51_CMD_SEND_FN = 0x15, // Not used currently + LKBT51_CMD_SEND_MOUSE = 0x16, + LKBT51_CMD_SEND_BOOT_KB = 0x17, + /* Bluetooth connections */ + LKBT51_CMD_PAIRING = 0x21, + LKBT51_CMD_CONNECT = 0x22, + LKBT51_CMD_DISCONNECT = 0x23, + LKBT51_CMD_SWITCH_HOST = 0x24, + LKBT51_CMD_READ_STATE_REG = 0x25, + /* Battery */ + LKBT51_CMD_BATTERY_MANAGE = 0x31, + LKBT51_CMD_UPDATE_BAT_LVL = 0x32, + LKBT51_CMD_UPDATE_BAT_STATE = 0x33, + /* Set/get parameters */ + LKBT51_CMD_GET_MODULE_INFO = 0x40, + LKBT51_CMD_SET_CONFIG = 0x41, + LKBT51_CMD_GET_CONFIG = 0x42, + LKBT51_CMD_SET_BDA = 0x43, + LKBT51_CMD_GET_BDA = 0x44, + LKBT51_CMD_SET_NAME = 0x45, + LKBT51_CMD_GET_NAME = 0x46, + LKBT51_CMD_WRTE_CSTM_DATA = 0x49, + /* DFU */ + LKBT51_CMD_GET_DFU_VER = 0x60, + LKBT51_CMD_HAND_SHAKE_TOKEN = 0x61, + LKBT51_CMD_START_DFU = 0x62, + LKBT51_CMD_SEND_FW_DATA = 0x63, + LKBT51_CMD_VERIFY_CRC32 = 0x64, + LKBT51_CMD_SWITCH_FW = 0x65, + /* Factory test */ + LKBT51_CMD_FACTORY_RESET = 0x71, + LKBT51_CMD_IO_TEST = 0x72, + LKBT51_CMD_RADIO_TEST = 0x73, + /* Event */ + LKBT51_EVT_LKBT51_CMD_RECEIVED = 0xA1, + LKBT51_EVT_OTA_RSP = 0xA3, + LKBT51_CONNECTION_EVT_ACK = 0xA4, +}; + +enum { + LKBT51_EVT_ACK = 0xA1, + LKBT51_EVT_QUERY_RSP = 0xA2, + LKBT51_EVT_RESET = 0xB0, + LKBT51_EVT_LE_CONNECTION = 0xB1, + LKBT51_EVT_HOST_TYPE = 0xB2, + LKBT51_EVT_CONNECTION = 0xB3, + LKBT51_EVT_HID_EVENT = 0xB4, + LKBT51_EVT_BATTERY = 0xB5, +}; + +enum { + LKBT51_CONNECTED = 0x20, + LKBT51_DISCOVERABLE = 0x21, + LKBT51_RECONNECTING = 0x22, + LKBT51_DISCONNECTED = 0x23, + LKBT51_PINCODE_ENTRY = 0x24, + LKBT51_EXIT_PINCODE_ENTRY = 0x25, + LKBT51_SLEEP = 0x26 +}; + +enum { + ACK_SUCCESS = 0x00, + ACK_CHECKSUM_ERROR, + ACK_FIFO_HALF_WARNING, + ACK_FIFO_FULL_ERROR, +}; + +enum{ + LK_EVT_MSK_CONNECTION = 0x01 << 0, + LK_EVT_MSK_LED = 0x01 << 1, + LK_EVT_MSK_BATT = 0x01 << 2, + LK_EVT_MSK_RESET = 0x01 << 3, + LK_EVT_MSK_RPT_INTERVAL = 0x01 << 4, + LK_EVT_MSK_MD = 0x01 << 7, +}; + +// clang-format on + +static uint8_t payload[PACKET_MAX_LEN]; +static uint8_t reg_offset = 0xFF; +static uint8_t expect_len = 22; +static uint16_t connection_interval = 1; +static uint32_t wake_time; + +// clang-format off +wt_func_t wireless_transport = { + lkbt51_init, + lkbt51_connect, + lkbt51_become_discoverable, + lkbt51_disconnect, + lkbt51_send_keyboard, + lkbt51_send_nkro, + lkbt51_send_consumer, + lkbt51_send_system, + lkbt51_send_mouse, + lkbt51_update_bat_lvl, + lkbt51_task +}; +// clang-format on + +/* Init SPI */ +const SPIConfig spicfg = { + .circular = false, + .slave = false, + .data_cb = NULL, + .error_cb = NULL, + .ssport = PAL_PORT(BLUETOOTH_INT_OUTPUT_PIN), + .sspad = PAL_PAD(BLUETOOTH_INT_OUTPUT_PIN), + .cr1 = SPI_CR1_MSTR | SPI_CR1_BR_1 | SPI_CR1_BR_0, + .cr2 = 0U, +}; + +void lkbt51_init(bool wakeup_from_low_power_mode) { +#ifdef LKBT51_RESET_PIN + if (!wakeup_from_low_power_mode) { + setPinOutput(LKBT51_RESET_PIN); + writePinLow(LKBT51_RESET_PIN); + wait_ms(1); + writePinHigh(LKBT51_RESET_PIN); + } +#endif + +#if (HAL_USE_SPI == TRUE) + if (WT_DRIVER.state == SPI_UNINIT) { + setPinOutput(SPI_SCK_PIN); + writePinHigh(SPI_SCK_PIN); + + palSetLineMode(SPI_SCK_PIN, PAL_MODE_ALTERNATE(SPI_CLK_PAL_MODE)); + palSetLineMode(SPI_MISO_PIN, PAL_MODE_ALTERNATE(SPI_MISO_PAL_MODE)); + palSetLineMode(SPI_MOSI_PIN, PAL_MODE_ALTERNATE(SPI_MOSI_PAL_MODE)); + + if (wakeup_from_low_power_mode) { + spiInit(); + return; + } + + spiInit(); + } +#endif + + setPinOutput(BLUETOOTH_INT_OUTPUT_PIN); + writePinHigh(BLUETOOTH_INT_OUTPUT_PIN); + + setPinInputHigh(LKBT51_INT_INPUT_PIN); +} + +static inline void lkbt51_wake(void) { + if (timer_elapsed32(wake_time) > 3000) { + wake_time = timer_read32(); + + palWriteLine(BLUETOOTH_INT_OUTPUT_PIN, 0); + wait_ms(10); + palWriteLine(BLUETOOTH_INT_OUTPUT_PIN, 1); + wait_ms(300); + } +} + +void lkbt51_send_protocol_ver(uint16_t ver) { + uint8_t pkt[PACKET_MAX_LEN] = {0}; + memset(pkt, 0, PACKET_MAX_LEN); + + uint8_t i = 0; + + pkt[i++] = 0x84; + pkt[i++] = 0x7e; + pkt[i++] = 0x00; + pkt[i++] = 0x00; + pkt[i++] = 0xAA; + pkt[i++] = 0x54; + pkt[i++] = ver & 0xFF; + pkt[i++] = (ver >> 8) & 0xFF; + pkt[i++] = (uint8_t)(~0x54); + pkt[i++] = (uint8_t)(~0xAA); + +#if HAL_USE_SPI + expect_len = 10; + spiStart(&WT_DRIVER, &spicfg); + spiSelect(&WT_DRIVER); + spiSend(&WT_DRIVER, i, pkt); + spiUnselectI(&WT_DRIVER); + spiStop(&WT_DRIVER); +#endif +} + +void lkbt51_send_cmd(uint8_t* payload, uint8_t len, bool ack_enable, bool retry) { + static uint8_t sn = 0; + uint8_t i; + uint8_t pkt[PACKET_MAX_LEN] = {0}; + memset(pkt, 0, PACKET_MAX_LEN); + + if (!retry) ++sn; + if (sn == 0) ++sn; + + uint16_t checksum = 0; + for (i = 0; i < len; i++) + checksum += payload[i]; + + i = 0; + pkt[i++] = 0x84; + pkt[i++] = 0x7e; + pkt[i++] = 0x00; + pkt[i++] = 0x00; + pkt[i++] = 0xAA; + pkt[i++] = ack_enable ? 0x56 : 0x55; + pkt[i++] = len + 2; + pkt[i++] = ~(len + 2) & 0xFF; + pkt[i++] = sn; + + memcpy(pkt + i, payload, len); + i += len; + pkt[i++] = checksum & 0xFF; + pkt[i++] = (checksum >> 8) & 0xFF; +#if HAL_USE_SPI + if ((payload[0] & 0xF0) == 0x60) + expect_len = 64; + else + expect_len = 64; + + spiStart(&WT_DRIVER, &spicfg); + spiSelect(&WT_DRIVER); + spiSend(&WT_DRIVER, i, pkt); + spiUnselectI(&WT_DRIVER); + spiStop(&WT_DRIVER); +#endif +} + +void lkbt51_read(uint8_t* payload, uint8_t len) { + uint8_t i; + uint8_t pkt[PACKET_MAX_LEN] = {0}; + memset(pkt, 0, PACKET_MAX_LEN); + + i = 0; + pkt[i++] = 0x84; + pkt[i++] = 0x7f; + pkt[i++] = 0x00; + pkt[i++] = 0x80; + + i += len; + +#if HAL_USE_SPI + spiStart(&WT_DRIVER, &spicfg); + spiSelect(&WT_DRIVER); + spiExchange(&WT_DRIVER, i, pkt, payload); + spiUnselect(&WT_DRIVER); + spiStop(&WT_DRIVER); +#endif +} + +void lkbt51_send_keyboard(uint8_t* report) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_SEND_KB; + memcpy(payload + i, report, 8); + i += 8; + + lkbt51_send_cmd(payload, i, true, false); +} + +void lkbt51_send_nkro(uint8_t* report) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_SEND_KB_NKRO; + memcpy(payload + i, report, 20); // NKRO report lenght is limited to 20 bytes + i += 20; + + lkbt51_send_cmd(payload, i, true, false); +} + +void lkbt51_send_consumer(uint16_t report) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_SEND_CONSUMER; + payload[i++] = report & 0xFF; + payload[i++] = ((report) >> 8) & 0xFF; + i += 4; // QMK doesn't send multiple consumer reports, just skip 2nd and 3rd consumer reports + + lkbt51_send_cmd(payload, i, true, false); +} + +void lkbt51_send_system(uint16_t report) { + uint8_t hid_usage = report & 0xFF; + + if (hid_usage < 0x81 || hid_usage > 0x83) return; + + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_SEND_SYSTEM; + payload[i++] = 0x01 << (hid_usage - 0x81); + + lkbt51_send_cmd(payload, i, true, false); +} + +void lkbt51_send_mouse(uint8_t* report) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_SEND_MOUSE; // Cmd type + payload[i++] = report[1]; // Button + payload[i++] = report[2]; // X + payload[i++] = (report[2] & 0x80) ? 0xff : 0x00; // ckbt51 use 16bit report, set high byte + payload[i++] = report[3]; // Y + payload[i++] = (report[3] & 0x80) ? 0xff : 0x00; // ckbt51 use 16bit report, set high byte + payload[i++] = report[4]; // V wheel + payload[i++] = report[5]; // H wheel + + lkbt51_send_cmd(payload, i, false, false); +} + +/* Send ack to connection event, wireless module will retry 2 times if no ack received */ +void lkbt51_send_conn_evt_ack(void) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CONNECTION_EVT_ACK; + + lkbt51_send_cmd(payload, i, false, false); +} + +void lkbt51_become_discoverable(uint8_t host_idx, void* param) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + pairing_param_t default_pairing_param = {0, 0, PAIRING_MODE_LESC_OR_SSP, BT_MODE_CLASSIC, 0, NULL}; + + if (param == NULL) { + param = &default_pairing_param; + } + pairing_param_t* p = (pairing_param_t*)param; + + payload[i++] = LKBT51_CMD_PAIRING; // Cmd type + payload[i++] = host_idx; // Host Index + payload[i++] = p->timeout & 0xFF; // Timeout + payload[i++] = (p->timeout >> 8) & 0xFF; + payload[i++] = p->pairingMode; + payload[i++] = p->BRorLE; // BR/LE + payload[i++] = p->txPower; // LE TX POWER + if (p->leName) { + memcpy(&payload[i], p->leName, strlen(p->leName)); + i += strlen(p->leName); + } + + lkbt51_wake(); + lkbt51_send_cmd(payload, i, true, false); +} + +/* Timeout : 2 ~ 255 seconds */ +void lkbt51_connect(uint8_t hostIndex, uint16_t timeout) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_CONNECT; + payload[i++] = hostIndex; // Host index + payload[i++] = timeout & 0xFF; // Timeout + payload[i++] = (timeout >> 8) & 0xFF; + + lkbt51_wake(); + lkbt51_send_cmd(payload, i, true, false); +} + +void lkbt51_disconnect(void) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_DISCONNECT; + payload[i++] = 0; // Sleep mode + + spiSelect(&SPID1); + wait_ms(30); + // spiUnselect(&SPID1); + wait_ms(70); + + lkbt51_send_cmd(payload, i, true, false); +} + +void lkbt51_switch_host(uint8_t hostIndex) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_SWITCH_HOST; + payload[i++] = hostIndex; + + lkbt51_send_cmd(payload, i, true, false); +} + +void lkbt51_read_state_reg(uint8_t reg, uint8_t len) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_READ_STATE_REG; + payload[i++] = reg_offset = reg; + payload[i++] = len; + + // TODO + lkbt51_send_cmd(payload, i, false, false); +} + +void lkbt51_update_bat_lvl(uint8_t bat_lvl) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_UPDATE_BAT_LVL; + payload[i++] = bat_lvl; + lkbt51_send_cmd(payload, i, false, false); +} + +void lkbt51_update_bat_state(uint8_t bat_state) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_UPDATE_BAT_STATE; + payload[i++] = bat_state; + lkbt51_send_cmd(payload, i, false, false); +} + +void lkbt51_get_info(module_info_t* info) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_GET_MODULE_INFO; + lkbt51_send_cmd(payload, i, false, false); +} + +void lkbt51_set_param(module_param_t* param) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_SET_CONFIG; + memcpy(payload + i, param, sizeof(module_param_t)); + i += sizeof(module_param_t); + + lkbt51_send_cmd(payload, i, false, false); +} + +void lkbt51_get_param(module_param_t* param) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_GET_CONFIG; + + lkbt51_send_cmd(payload, i, false, false); +} + +void lkbt51_set_local_name(const char* name) { + uint8_t i = 0; + uint8_t len = strlen(name); + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_SET_NAME; + memcpy(payload + i, name, len); + i += len; + lkbt51_send_cmd(payload, i, false, false); +} + +void lkbt51_get_local_name(void) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_GET_NAME; + + lkbt51_send_cmd(payload, i, false, false); +} + +void lkbt51_factory_reset(uint8_t p2p4g_clr_msk) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + + payload[i++] = LKBT51_CMD_FACTORY_RESET; + payload[i++] = p2p4g_clr_msk; + + lkbt51_wake(); + lkbt51_send_cmd(payload, i, false, false); +} + +void lkbt51_int_pin_test(bool enable) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + payload[i++] = LKBT51_CMD_IO_TEST; + payload[i++] = enable; + + lkbt51_send_cmd(payload, i, false, false); +} + +void lkbt51_radio_test(uint8_t channel) { + uint8_t i = 0; + memset(payload, 0, PACKET_MAX_LEN); + payload[i++] = LKBT51_CMD_RADIO_TEST; + payload[i++] = channel; + payload[i++] = 0; + + lkbt51_send_cmd(payload, i, false, false); +} + +bool lkbt51_read_customize_data(uint8_t* data, uint8_t len) { + uint8_t i; + uint8_t buf[20] = {0}; + + i = 0; + buf[i++] = 0x84; + buf[i++] = 0x7a; + buf[i++] = 0x00; + buf[i++] = 0x80; + +#if HAL_USE_SPI + spiStart(&WT_DRIVER, &spicfg); + spiSelect(&WT_DRIVER); + spiExchange(&WT_DRIVER, 20, buf, payload); + uint16_t state = buf[5] | (buf[6] << 8); + if (state == 0x9527) spiExchange(&WT_DRIVER, len, data, payload); + spiUnselect(&WT_DRIVER); + spiStop(&WT_DRIVER); +#endif + + return true; +} + +void lkbt51_write_customize_data(uint8_t* data, uint8_t len) { + uint8_t i; + uint8_t pkt[PACKET_MAX_LEN] = {0}; + + i = 0; + pkt[i++] = 0x84; + pkt[i++] = 0x7a; + pkt[i++] = 0x00; + pkt[i++] = 0x00; + +#if HAL_USE_SPI + spiStart(&WT_DRIVER, &spicfg); + spiSelect(&WT_DRIVER); + spiSend(&WT_DRIVER, i, pkt); + spiSend(&WT_DRIVER, len, data); + spiUnselectI(&WT_DRIVER); + spiStop(&WT_DRIVER); +#endif + + i = 0; + memset(payload, 0, PACKET_MAX_LEN); + payload[i++] = LKBT51_CMD_WRTE_CSTM_DATA; + + lkbt51_send_cmd(payload, i, false, false); +} +#ifdef RAW_ENABLE +void lkbt51_dfu_tx(uint8_t rsp, uint8_t* data, uint8_t len, uint8_t sn) { + uint16_t checksum = 0; + uint8_t buf[RAW_EPSIZE] = {0}; + uint8_t i = 0; + + buf[i++] = 0x03; + buf[i++] = 0xAA; + buf[i++] = 0x57; + buf[i++] = len; + buf[i++] = ~len; + buf[i++] = sn; + buf[i++] = rsp; + memcpy(&buf[i], data, len); + i += len; + + for (uint8_t k = 0; k < i; k++) + checksum += buf[i]; + + raw_hid_send(buf, RAW_EPSIZE); + + if (len > 25) { + i = 0; + memset(buf, 0, RAW_EPSIZE); + buf[i++] = 0x03; + memcpy(&buf[i], data + 25, len - 25); + i = i + len - 25; + raw_hid_send(buf, RAW_EPSIZE); + } +} +#endif +void lkbt51_dfu_rx(uint8_t* data, uint8_t length) { + if (data[0] == 0xAA && (data[1] == 0x55 || data[1] == 0x56) && data[2] == (~data[3] & 0xFF)) { + uint16_t checksum = 0; + uint8_t payload_len = data[2]; + + /* Check payload_len validity */ + if (payload_len > RAW_EPSIZE - PACKECT_HEADER_LEN) return; + + uint8_t* payload = &data[PACKECT_HEADER_LEN]; + + for (uint8_t i = 0; i < payload_len - 2; i++) { + checksum += payload[i]; + } + + /* Verify checksum */ + if ((checksum & 0xFF) != payload[payload_len - 2] || checksum >> 8 != payload[payload_len - 1]) return; + static uint8_t sn = 0; + + bool retry = true; + if (sn != data[4]) { + sn = data[4]; + retry = false; + } + + if ((payload[0] & 0xF0) == 0x60) { + lkbt51_wake(); + lkbt51_send_cmd(payload, payload_len - 2, data[1] == 0x56, retry); + } + } +} + +static void ack_handler(uint8_t* data, uint8_t len) { + switch (data[1]) { + case LKBT51_CMD_SEND_KB: + case LKBT51_CMD_SEND_KB_NKRO: + case LKBT51_CMD_SEND_CONSUMER: + case LKBT51_CMD_SEND_SYSTEM: + case LKBT51_CMD_SEND_MOUSE: + switch (data[2]) { + case ACK_SUCCESS: + report_buffer_set_retry(0); + report_buffer_set_inverval(connection_interval); + break; + case ACK_FIFO_HALF_WARNING: + report_buffer_set_retry(0); + report_buffer_set_inverval(connection_interval + 5); + break; + case ACK_FIFO_FULL_ERROR: + report_buffer_set_inverval(connection_interval + 10); + break; + } + break; + default: + break; + } +} + +static void query_rsp_handler(uint8_t* data, uint8_t len) { + if (data[2]) return; + + switch (data[1]) { + case LKBT51_CMD_IO_TEST: + factory_test_send(data, len); + break; + default: + break; + } +} + +static void lkbt51_event_handler(uint8_t evt_type, uint8_t* data, uint8_t len, uint8_t sn) { + wireless_event_t event = {0}; + + switch (evt_type) { + case LKBT51_EVT_ACK: + ack_handler(data, len); + break; + case LKBT51_EVT_RESET: + kc_printf("LKBT51_EVT_RESET\n"); + event.evt_type = EVT_RESET; + event.params.reason = data[0]; + break; + case LKBT51_EVT_LE_CONNECTION: + kc_printf("LKBT51_EVT_LE_CONNECTION\n"); + break; + case LKBT51_EVT_HOST_TYPE: + kc_printf("LKBT51_EVT_HOST_TYPE\n"); + break; + case LKBT51_EVT_HID_EVENT: + kc_printf("LKBT51_EVT_HID_EVENT\n"); + event.evt_type = EVT_HID_INDICATOR; + event.params.led = data[0]; + break; + case LKBT51_EVT_QUERY_RSP: + kc_printf("LKBT51_EVT_QUERY_RSP\n\r"); + query_rsp_handler(data, len); + break; + case LKBT51_EVT_OTA_RSP: +#ifdef RAW_ENABLE + kc_printf("LKBT51_EVT_OTA_RSP\n"); + lkbt51_dfu_tx(LKBT51_EVT_OTA_RSP, data, len, sn); +#endif + break; + default: + kc_printf("Unknown event!!!\n"); + break; + } + + if (event.evt_type) wireless_event_enqueue(event); +} + +void lkbt51_task(void) { +#define VALID_DATA_START_INDEX 4 +#define BUFFER_SIZE 64 + + static bool wait_for_new_pkt = true; + static uint8_t len = 0xff; + static uint8_t sn = 0; + + if (readPin(LKBT51_INT_INPUT_PIN) == 0) { + uint8_t buf[BUFFER_SIZE] = {0}; + lkbt51_read(buf, expect_len); + + uint8_t* pbuf = buf + VALID_DATA_START_INDEX; + + if (pbuf[0] == 0xAA && pbuf[1] == 0x54 && pbuf[4] == (uint8_t)(~0x54) && pbuf[5] == (uint8_t)(~0xAA)) { + uint16_t protol_ver = pbuf[3] << 8 | pbuf[2]; + kc_printf("protol_ver: %x\n\r", protol_ver); + (void)protol_ver; + } else if (pbuf[0] == 0xAA) { + wireless_event_t event = {0}; + uint8_t evt_mask = pbuf[1]; + + if (evt_mask & LK_EVT_MSK_RESET) { + event.evt_type = EVT_RESET; + event.params.reason = pbuf[2]; + wireless_event_enqueue(event); + } + + if (evt_mask & LK_EVT_MSK_CONNECTION) { + lkbt51_send_conn_evt_ack(); + switch (pbuf[2]) { + case LKBT51_CONNECTED: + event.evt_type = EVT_CONNECTED; + break; + case LKBT51_DISCOVERABLE: + event.evt_type = EVT_DISCOVERABLE; + break; + case LKBT51_RECONNECTING: + event.evt_type = EVT_RECONNECTING; + break; + case LKBT51_DISCONNECTED: + event.evt_type = EVT_DISCONNECTED; + break; + case LKBT51_PINCODE_ENTRY: + event.evt_type = EVT_BT_PINCODE_ENTRY; + break; + case LKBT51_EXIT_PINCODE_ENTRY: + event.evt_type = EVT_EXIT_BT_PINCODE_ENTRY; + break; + case LKBT51_SLEEP: + event.evt_type = EVT_SLEEP; + break; + } + event.params.hostIndex = pbuf[3]; + + wireless_event_enqueue(event); + } + + if (evt_mask & LK_EVT_MSK_LED) { + memset(&event, 0, sizeof(event)); + event.evt_type = EVT_HID_INDICATOR; + event.params.led = pbuf[4]; + wireless_event_enqueue(event); + } + + if (evt_mask & LK_EVT_MSK_RPT_INTERVAL) { + uint32_t interval; + if (pbuf[8] & 0x80) { + interval = (pbuf[8] & 0x7F) * 1250; + } else { + interval = (pbuf[8] & 0x7F) * 125; + } + + connection_interval = interval / 1000; + if (connection_interval > 7) connection_interval /= 3; + + memset(&event, 0, sizeof(event)); + event.evt_type = EVT_CONECTION_INTERVAL; + event.params.interval = connection_interval; + wireless_event_enqueue(event); + } + + if (evt_mask & LK_EVT_MSK_BATT) { + battery_calculate_voltage(true, pbuf[6] << 8 | pbuf[5]); + } + } + + pbuf = buf; + if (wait_for_new_pkt) { + for (uint8_t i = 10; i < BUFFER_SIZE - 5; i++) { + if (buf[i] == 0xAA && buf[i + 1] == 0x57 // Packet Head + && (~buf[i + 2] & 0xFF) == buf[i + 3]) { // Check wheather len is valid + len = buf[i + 2]; + sn = buf[i + 4]; + pbuf = &buf[i + 5]; + wait_for_new_pkt = false; + } + } + } + + if (!wait_for_new_pkt && BUFFER_SIZE - 5 >= len) { + wait_for_new_pkt = true; + + uint16_t checksum = 0; + for (int i = 0; i < len - 2; i++) { + checksum += pbuf[i]; + } + + if ((checksum & 0xff) == pbuf[len - 2] && ((checksum >> 8) & 0xff) == pbuf[len - 1]) { + lkbt51_event_handler(pbuf[0], pbuf + 1, len - 3, sn); + } else { + // TODO: Error handle + } + } + } +} diff --git a/keyboards/lemokey/common/wireless/lkbt51.h b/keyboards/lemokey/common/wireless/lkbt51.h new file mode 100644 index 00000000000..f5380f12be8 --- /dev/null +++ b/keyboards/lemokey/common/wireless/lkbt51.h @@ -0,0 +1,131 @@ +/* Copyright 2023 @ Keychron (https://www.keychron.com) + * + * 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 "stdint.h" +#include "hal.h" + +#ifndef WT_DRIVER +# define WT_DRIVER SPID1 +#endif + +// Error checking +#if HAL_USE_SPI == FALSE +# error "Please enable SPI to use LKBT51" +#endif + +#if !STM32_SPI_USE_SPI1 && !STM32_SPI_USE_SPI2 && !STM32_SPI_USE_SPI3 +# error "WT driver activated but no SPI peripheral assigned" +#endif + +#define PACKECT_HEADER_LEN 5 +#define BDA_LEN 6 +#define PACKET_MAX_LEN 64 +#define P24G_INDEX 24 + +enum { + PAIRING_MODE_DEFAULT = 0x00, + PAIRING_MODE_JUST_WORK, + PAIRING_MODE_PASSKEY_ENTRY, + PAIRING_MODE_LESC_OR_SSP, + PAIRING_MODE_INVALID, +}; + +enum { + BT_MODE_DEFAUL, + BT_MODE_CLASSIC, + BT_MODE_LE, + BT_MODE_INVALID, +}; + +typedef struct { + uint8_t hostIndex; + uint16_t timeout; /* Pairing timeout, valid value range from 30 to 3600 seconds, 0 for default */ + uint8_t pairingMode; /* 0: default, 1: Just Works, 2: Passkey Entry */ + uint8_t BRorLE; /* Only available for dual mode module. Keep 0 for single mode module */ + uint8_t txPower; /* Only available for BLE module */ + const char* leName; /* Only available for BLE module */ +} pairing_param_t; + +typedef struct { + uint8_t type; + uint16_t full_votage; + uint16_t empty_voltage; + uint16_t shutdown_voltage; +} battery_param_t; + +typedef struct { + uint8_t model_name[11]; + uint8_t mode; + uint8_t bluetooth_version; + uint8_t firmware_version[11]; + uint8_t hardware_version[11]; + uint16_t cmd_set_verson; +} __attribute__((packed)) module_info_t; + +typedef struct { + uint8_t event_mode; /* Must be 0x02 */ + uint16_t connected_idle_timeout; + uint16_t pairing_timeout; /* Range: 30 ~ 3600 second, 0 for default */ + uint8_t pairing_mode; /* 0: default, 1: Just Works, 2: Passkey Entry */ + uint16_t reconnect_timeout; /* 0: default, 0xFF: Unlimited time, 2 ~ 254 seconds */ + uint8_t report_rate; /* 90 or 133 */ + uint8_t rsvd1; + uint8_t rsvd2; + uint8_t vendor_id_source; /* 0: From Bluetooth SIG, 1: From USB-IF */ + uint16_t verndor_id; /* No effect, the vendor ID is 0x362D*/ + uint16_t product_id; + /* Below parametes is only available for BLE module */ + uint16_t le_connection_interval_min; + uint16_t le_connection_interval_max; + uint16_t le_connection_interval_timeout; +} __attribute__((packed)) module_param_t; + +void lkbt51_init(bool wakeup_from_low_power_mode); +void lkbt51_send_protocol_ver(uint16_t ver); + +void lkbt51_send_cmd(uint8_t* payload, uint8_t len, bool ack_enable, bool retry); + +void lkbt51_send_keyboard(uint8_t* report); +void lkbt51_send_nkro(uint8_t* report); +void lkbt51_send_consumer(uint16_t report); +void lkbt51_send_system(uint16_t report); +void lkbt51_send_mouse(uint8_t* report); + +void lkbt51_become_discoverable(uint8_t host_idx, void* param); +void lkbt51_connect(uint8_t hostIndex, uint16_t timeout); +void lkbt51_disconnect(void); +void lkbt51_switch_host(uint8_t hostIndex); +void lkbt51_read_state_reg(uint8_t reg, uint8_t len); + +void lkbt51_update_bat_lvl(uint8_t bat_lvl); +void lkbt51_update_bat_state(uint8_t bat_state); + +void lkbt51_get_info(module_info_t* info); +void lkbt51_set_param(module_param_t* param); +void lkbt51_get_param(module_param_t* param); +void lkbt51_set_local_name(const char* name); +void lkbt51_get_local_name(void); + +void lkbt51_factory_reset(uint8_t p2p4g_clr_msk); +void lkbt51_int_pin_test(bool enable); +void lkbt51_dfu_rx(uint8_t* data, uint8_t length); +void lkbt51_radio_test(uint8_t channel); +void lkbt51_write_customize_data(uint8_t* data, uint8_t len); +bool lkbt51_read_customize_data(uint8_t* data, uint8_t len); + +void lkbt51_task(void); diff --git a/keyboards/lemokey/common/wireless/lpm.c b/keyboards/lemokey/common/wireless/lpm.c new file mode 100644 index 00000000000..d124722d9cf --- /dev/null +++ b/keyboards/lemokey/common/wireless/lpm.c @@ -0,0 +1,277 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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 . + */ + +/****************************************************************************** + * + * Filename: lpm.c + * + * Description: Contains low power mode implementation + * + ******************************************************************************/ + +#include "quantum.h" +#if defined(PROTOCOL_CHIBIOS) +# include +#endif +#include "debounce.h" +#include "wireless.h" +#include "indicator.h" +#include "lpm.h" +#include "transport.h" +#include "battery.h" +#include "report_buffer.h" +#include "lemokey_common.h" + +extern matrix_row_t matrix[MATRIX_ROWS]; +extern wt_func_t wireless_transport; + +static uint32_t lpm_timer_buffer; +static bool lpm_time_up = false; +#ifndef OPTICAL_SWITCH +static matrix_row_t empty_matrix[MATRIX_ROWS] = {0}; +#endif + +pin_t pins_row[MATRIX_ROWS] = MATRIX_ROW_PINS; +pin_t pins_col[MATRIX_COLS] = MATRIX_COL_PINS; +; + +__attribute__((weak)) void select_all_cols(void) { + for (uint8_t i = 0; i < MATRIX_COLS; i++) { + setPinOutput(pins_col[i]); + writePinLow(pins_col[i]); + } +} + +void lpm_init(void) { +#ifdef USB_POWER_SENSE_PIN +# if (USB_POWER_CONNECTED_LEVEL == 0) + setPinInputHigh(USB_POWER_SENSE_PIN); +# else + setPinInputLow(USB_POWER_SENSE_PIN); +# endif +#endif + lpm_timer_reset(); +} + +inline void lpm_timer_reset(void) { + lpm_time_up = false; + lpm_timer_buffer = timer_read32(); +} + +void lpm_timer_stop(void) { + lpm_time_up = false; + lpm_timer_buffer = 0; +} + +static inline bool lpm_any_matrix_action(void) { +#ifdef OPTICAL_SWITCH + bool any_key = false; + for (uint8_t i = 0; i < MATRIX_ROWS; i++) + if (matrix_get_row(i) != 0) { + any_key = true; + } + return any_key; +#else + return memcmp(matrix, empty_matrix, sizeof(empty_matrix)); +#endif +} + +/* Implement of entering low power mode and wakeup varies per mcu or platform */ +__attribute__((weak)) void enter_power_mode(pm_t mode) {} + +__attribute__((weak)) bool usb_power_connected(void) { +#ifdef USB_POWER_SENSE_PIN + return readPin(USB_POWER_SENSE_PIN) == USB_POWER_CONNECTED_LEVEL; +#else + return true; +#endif +} + +__attribute__((weak)) bool lpm_is_kb_idle(void) { + return true; +} + +__attribute__((weak)) bool lpm_set(pm_t mode) { + return false; +} + +bool pre_enter_low_power_mode(pm_t mode) { +#if defined(KEEP_USB_CONNECTION_IN_WIRELESS_MODE) + /* Don't enter low power mode if attached to the host */ + if (mode > PM_SLEEP && usb_power_connected()) return false; +#endif + + if (!lpm_set(mode)) return false; + +#if defined(KEEP_USB_CONNECTION_IN_WIRELESS_MODE) + /* Usb unit is actived and running, stop and disconnect first */ + usbStop(&USBD1); + usbDisconnectBus(&USBD1); + + /* Isolate USB to save power.*/ + // PWR->CR2 &= ~PWR_CR2_USV; /*PWR_CR2_USV is available on STM32L4x2xx and STM32L4x3xx devices only. */ +#endif + + palEnableLineEvent(LKBT51_INT_INPUT_PIN, PAL_EVENT_MODE_FALLING_EDGE); +#ifdef USB_POWER_SENSE_PIN + palEnableLineEvent(USB_POWER_SENSE_PIN, PAL_EVENT_MODE_BOTH_EDGES); +#endif +#ifdef P2P4_MODE_SELECT_PIN + palEnableLineEvent(P2P4_MODE_SELECT_PIN, PAL_EVENT_MODE_BOTH_EDGES); +#endif +#ifdef BT_MODE_SELECT_PIN + palEnableLineEvent(BT_MODE_SELECT_PIN, PAL_EVENT_MODE_BOTH_EDGES); +#endif + +#ifdef OPTICAL_SWITCH + + for (uint8_t x = 0; x < MATRIX_ROWS; x++) { + if (pins_row[x] != NO_PIN) { + writePinLow(pins_row[x]); + } + } + + for (uint8_t x = 0; x < MATRIX_COLS; x++) { + if (pins_col[x] != NO_PIN) { + setPinInputLow(pins_col[x]); + } + } +#else + + /* Enable key matrix wake up */ + for (uint8_t x = 0; x < MATRIX_ROWS; x++) { + if (pins_row[x] != NO_PIN) { + palEnableLineEvent(pins_row[x], PAL_EVENT_MODE_BOTH_EDGES); + } + } +#endif + select_all_cols(); + +#if (HAL_USE_SPI == TRUE) + palSetLineMode(SPI_SCK_PIN, PAL_MODE_INPUT_PULLDOWN); + palSetLineMode(SPI_MISO_PIN, PAL_MODE_INPUT_PULLDOWN); + palSetLineMode(SPI_MOSI_PIN, PAL_MODE_INPUT_PULLDOWN); +#endif + palSetLineMode(A12, PAL_MODE_INPUT_PULLDOWN); + palSetLineMode(A11, PAL_MODE_INPUT_PULLDOWN); + +#if defined(DIP_SWITCH_PINS) +# define NUMBER_OF_DIP_SWITCHES (sizeof(dip_switch_pad) / sizeof(pin_t)) + static pin_t dip_switch_pad[] = DIP_SWITCH_PINS; + + for (uint8_t i = 0; i < NUMBER_OF_DIP_SWITCHES; i++) { + setPinInputLow(dip_switch_pad[i]); + } +#endif + battery_stop(); + + return true; +} + +static inline void lpm_wakeup(void) { + palSetLineMode(A11, PAL_STM32_OTYPE_PUSHPULL | PAL_STM32_OSPEED_HIGHEST | PAL_STM32_PUPDR_FLOATING | PAL_MODE_ALTERNATE(10U)); + palSetLineMode(A12, PAL_STM32_OTYPE_PUSHPULL | PAL_STM32_OSPEED_HIGHEST | PAL_STM32_PUPDR_FLOATING | PAL_MODE_ALTERNATE(10U)); + +#if (HAL_USE_SPI == TRUE) + palSetLineMode(SPI_SCK_PIN, PAL_MODE_ALTERNATE(5)); + palSetLineMode(SPI_MISO_PIN, PAL_MODE_ALTERNATE(5)); + palSetLineMode(SPI_MOSI_PIN, PAL_MODE_ALTERNATE(5)); +#endif + + halInit(); + +#ifdef ENCODER_ENABLE + encoder_cb_init(); +#endif + + if (wireless_transport.init) wireless_transport.init(true); + battery_init(); + + /* Disable all wake up pins */ + for (uint8_t x = 0; x < MATRIX_ROWS; x++) { + if (pins_row[x] != NO_PIN) { + palDisableLineEvent(pins_row[x]); + } + } + + palDisableLineEvent(LKBT51_INT_INPUT_PIN); +#ifdef P2P4_MODE_SELECT_PIN + palDisableLineEvent(P2P4_MODE_SELECT_PIN); +#endif +#ifdef BT_MODE_SELECT_PIN + palDisableLineEvent(BT_MODE_SELECT_PIN); +#endif +#ifdef USB_POWER_SENSE_PIN + palDisableLineEvent(USB_POWER_SENSE_PIN); + +# if defined(KEEP_USB_CONNECTION_IN_WIRELESS_MODE) + if (usb_power_connected()) { + usb_event_queue_init(); + init_usb_driver(&USB_DRIVER); + } +# endif + +#endif + +#if defined(DIP_SWITCH_PINS) + dip_switch_init(); + dip_switch_read(true); +#endif + + /* Call debounce_free() to avoiding memory leak of debounce_counters as debounce_init() + invoked in matrix_init() alloc new memory to debounce_counters */ + debounce_free(); + matrix_init(); +} + +void lpm_task(void) { + if (!lpm_time_up && sync_timer_elapsed32(lpm_timer_buffer) > RUN_MODE_PROCESS_TIME) { + lpm_time_up = true; + lpm_timer_buffer = 0; + } + + if (usb_power_connected() && USBD1.state == USB_STOP) { + usb_event_queue_init(); + init_usb_driver(&USB_DRIVER); + } + + if ((get_transport() == TRANSPORT_BLUETOOTH || get_transport() == TRANSPORT_P2P4) && lpm_time_up && !indicator_is_running() && lpm_is_kb_idle()) { +#if defined(LED_MATRIX_ENABLE) || defined(RGB_MATRIX_ENABLE) + if ( +# ifdef LED_MATRIX_ENABLE + !led_matrix_is_enabled() || + (led_matrix_is_enabled() && led_matrix_is_driver_shutdown()) +# endif +# ifdef RGB_MATRIX_ENABLE + !rgb_matrix_is_enabled() || + (rgb_matrix_is_enabled() && rgb_matrix_is_driver_shutdown()) +# endif + ) +#endif + { + if (!lpm_any_matrix_action()) { + if (pre_enter_low_power_mode(LOW_POWER_MODE)) { + enter_power_mode(LOW_POWER_MODE); + + lpm_wakeup(); + lpm_timer_reset(); + report_buffer_init(); + lpm_set(PM_RUN); + } + } + } + } +} diff --git a/keyboards/lemokey/common/wireless/lpm.h b/keyboards/lemokey/common/wireless/lpm.h new file mode 100644 index 00000000000..ca6fc5d4507 --- /dev/null +++ b/keyboards/lemokey/common/wireless/lpm.h @@ -0,0 +1,36 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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 + +#ifndef RUN_MODE_PROCESS_TIME +# define RUN_MODE_PROCESS_TIME 1000 +#endif + +typedef enum { + PM_RUN, + PM_SLEEP, + PM_STOP, + PM_STANDBY, +} pm_t; + +void lpm_init(void); +void lpm_timer_reset(void); +void lpm_timer_stop(void); +bool usb_power_connected(void); +bool lpm_is_kb_idle(void); +void enter_power_mode(pm_t mode); +void lpm_task(void); diff --git a/keyboards/lemokey/common/wireless/lpm_stm32f401.c b/keyboards/lemokey/common/wireless/lpm_stm32f401.c new file mode 100644 index 00000000000..8b5255879c5 --- /dev/null +++ b/keyboards/lemokey/common/wireless/lpm_stm32f401.c @@ -0,0 +1,114 @@ +/* Copyright 2022 @ lokher (https://www.keychron.com) + * + * 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 . + */ + +/****************************************************************************** + * + * Filename: lpm_stm32f401.c + * + * Description: Contains low power mode implementation + * + ******************************************************************************/ + +#include "quantum.h" +#include +#include "wireless.h" +#include "lpm.h" +#include "lpm_stm32f401.h" +#include "config.h" + +static pm_t power_mode = PM_RUN; + +bool lpm_set(pm_t mode) { + bool ret = true; + + switch (mode) { + case PM_SLEEP: + /* Wake source: Any interrupt or event */ + if (power_mode != PM_RUN) + ret = false; + else + SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; + break; + + case PM_STOP: + /* Wake source: Reset pin, all I/Os, BOR, PVD, PVM, RTC, LCD, IWDG, + COMPx, USARTx, LPUART1, I2Cx, LPTIMx, USB, SWPMI */ + if (power_mode != PM_RUN) + ret = false; + else { + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + PWR->CR |= +#if STOP_MODE_MAIN_REGULATOR_LOW_VOLTAGE + PWR_CR_MRLVDS | +#endif +#if STOP_MODE_LOW_POWER_REGULATOR_LOW_VOLTAG + PWR_CR_LPLVDS | +#endif +#if STOP_MODE_FLASH_POWER_DOWN + PWR_CR_FPDS | +#endif +#if STOP_MODE_LOW_POWER_DEEPSLEEP + PWR_CR_LPDS | +#endif + 0; + } + break; + + case PM_STANDBY: + if (power_mode != PM_RUN) + ret = false; + else { + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + } + break; + + default: + break; + } + power_mode = mode; + + return ret; +} + +void enter_power_mode(pm_t mode) { +#if STM32_HSE_ENABLED + /* Switch to HSI */ + RCC->CFGR = (RCC->CFGR & (~STM32_SW_MASK)) | STM32_SW_HSI; + while ((RCC->CFGR & RCC_CFGR_SWS) != (STM32_SW_HSI << 2)) + ; + + /* Set HSE off */ + RCC->CR &= ~RCC_CR_HSEON; + while ((RCC->CR & RCC_CR_HSERDY)) + ; + + /* To avoid power consumption of floating GPIO */ + palSetLineMode(H0, PAL_MODE_INPUT_PULLDOWN); + palSetLineMode(H1, PAL_MODE_INPUT_PULLDOWN); +#endif + + __WFI(); + + SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; + + writePinLow(BLUETOOTH_INT_OUTPUT_PIN); + stm32_clock_init(); + writePinHigh(BLUETOOTH_INT_OUTPUT_PIN); +} + +void usb_power_connect(void) {} + +void usb_power_disconnect(void) {} diff --git a/keyboards/lemokey/common/wireless/lpm_stm32f401.h b/keyboards/lemokey/common/wireless/lpm_stm32f401.h new file mode 100644 index 00000000000..3b25c3d57c7 --- /dev/null +++ b/keyboards/lemokey/common/wireless/lpm_stm32f401.h @@ -0,0 +1,33 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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 + +#ifndef STOP_MODE_MAIN_REGULATOR_LOW_VOLTAGE +# define STOP_MODE_MAIN_REGULATOR_LOW_VOLTAGE TRUE +#endif + +#ifndef STOP_MODE_LOW_POWER_REGULATOR_LOW_VOLTAG +# define STOP_MODE_LOW_POWER_REGULATOR_LOW_VOLTAG TRUE +#endif + +#ifndef STOP_MODE_FLASH_POWER_DOWN +# define STOP_MODE_FLASH_POWER_DOWN TRUE +#endif + +#ifndef STOP_MODE_LOW_POWER_DEEPSLEEP +# define STOP_MODE_LOW_POWER_DEEPSLEEP TRUE +#endif diff --git a/keyboards/lemokey/common/wireless/report_buffer.c b/keyboards/lemokey/common/wireless/report_buffer.c new file mode 100644 index 00000000000..317ba8ce1dd --- /dev/null +++ b/keyboards/lemokey/common/wireless/report_buffer.c @@ -0,0 +1,144 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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" +#include "report_buffer.h" +#include "wireless.h" +#include "lpm.h" + +/* The report buffer is mainly used to fix key press lost issue of macro + * when wireless module fifo isn't large enough. The maximun macro + * string length is determined by this queue size, and should be + * REPORT_BUFFER_QUEUE_SIZE devided by 2 since each character is implemented + * by sending a key pressing then a key releasing report. + * Please note that it cosume sizeof(report_buffer_t) * REPORT_BUFFER_QUEUE_SIZE + * bytes RAM, with default setting, used RAM size is + * sizeof(report_buffer_t) * 256 = 34* 256 = 8704 bytes + */ +#ifndef REPORT_BUFFER_QUEUE_SIZE +# define REPORT_BUFFER_QUEUE_SIZE 256 +#endif + +extern wt_func_t wireless_transport; + +/* report_interval value should be less than bluetooth connection interval because + * it takes some time for communicating between mcu and bluetooth module. Carefully + * set this value to feed the bt module so that we don't lost the key report nor lost + * the anchor point of bluetooth interval. The bluetooth connection interval varies + * if BLE is used, invoke report_buffer_set_inverval() to update the value + */ +uint8_t report_interval = DEFAULT_2P4G_REPORT_INVERVAL_MS; + +static uint32_t report_timer_buffer = 0; +uint32_t retry_time_buffer = 0; +report_buffer_t report_buffer_queue[REPORT_BUFFER_QUEUE_SIZE]; +uint16_t report_buffer_queue_head; +uint16_t report_buffer_queue_tail; +report_buffer_t kb_rpt; +uint8_t retry = 0; + +void report_buffer_task(void); + +void report_buffer_init(void) { + // Initialise the report queue + memset(&report_buffer_queue, 0, sizeof(report_buffer_queue)); + report_buffer_queue_head = 0; + report_buffer_queue_tail = 0; + retry = 0; + report_timer_buffer = timer_read32(); +} + +bool report_buffer_enqueue(report_buffer_t *report) { + uint16_t next = (report_buffer_queue_head + 1) % REPORT_BUFFER_QUEUE_SIZE; + if (next == report_buffer_queue_tail) { + return false; + } + + report_buffer_queue[report_buffer_queue_head] = *report; + report_buffer_queue_head = next; + return true; +} + +inline bool report_buffer_dequeue(report_buffer_t *report) { + if (report_buffer_queue_head == report_buffer_queue_tail) { + return false; + } + + *report = report_buffer_queue[report_buffer_queue_tail]; + report_buffer_queue_tail = (report_buffer_queue_tail + 1) % REPORT_BUFFER_QUEUE_SIZE; + return true; +} + +bool report_buffer_is_empty() { + return report_buffer_queue_head == report_buffer_queue_tail; +} + +void report_buffer_update_timer(void) { + report_timer_buffer = timer_read32(); +} + +bool report_buffer_next_inverval(void) { + return timer_elapsed32(report_timer_buffer) > report_interval; +} + +void report_buffer_set_inverval(uint8_t interval) { + // OG_TRACE("report_buffer_set_inverval: %d\n\r", interval); + report_interval = interval; +} + +uint8_t report_buffer_get_retry(void) { + return retry; +} + +void report_buffer_set_retry(uint8_t times) { + retry = times; +} + +void report_buffer_task(void) { + if (wireless_get_state() == WT_CONNECTED && (!report_buffer_is_empty() || retry) && report_buffer_next_inverval()) { + bool pending_data = false; + + if (!retry) { + if (report_buffer_dequeue(&kb_rpt) && kb_rpt.type != REPORT_TYPE_NONE) { + if (timer_read32() > 2) { + pending_data = true; + retry = RETPORT_RETRY_COUNT; + retry_time_buffer = timer_read32(); + } + } + } else { + if (timer_elapsed32(retry_time_buffer) > 2) { + pending_data = true; + --retry; + retry_time_buffer = timer_read32(); + } + } + + if (pending_data) { +#if defined(NKRO_ENABLE) && defined(WIRELESS_NKRO_ENABLE) + if (kb_rpt.type == REPORT_TYPE_NKRO && wireless_transport.send_nkro) { + wireless_transport.send_nkro(&kb_rpt.nkro.mods); + } else if (kb_rpt.type == REPORT_TYPE_KB && wireless_transport.send_keyboard) + wireless_transport.send_keyboard(&kb_rpt.keyboard.mods); +#else + if (kb_rpt.type == REPORT_TYPE_KB && wireless_transport.send_keyboard) wireless_transport.send_keyboard(&kb_rpt.keyboard.mods); +#endif + if (kb_rpt.type == REPORT_TYPE_CONSUMER && wireless_transport.send_consumer) wireless_transport.send_consumer(kb_rpt.consumer); + report_timer_buffer = timer_read32(); + lpm_timer_reset(); + } + } +} diff --git a/keyboards/lemokey/common/wireless/report_buffer.h b/keyboards/lemokey/common/wireless/report_buffer.h new file mode 100644 index 00000000000..714959683a3 --- /dev/null +++ b/keyboards/lemokey/common/wireless/report_buffer.h @@ -0,0 +1,61 @@ +/* Copyright 2022 @ lokher (https://www.keychron.com) + * + * 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 "report.h" + +/* Default report interval value */ +#ifndef DEFAULT_BLE_REPORT_INVERVAL_MS +# define DEFAULT_BLE_REPORT_INVERVAL_MS 3 +#endif + +/* Default report interval value */ +#ifndef DEFAULT_2P4G_REPORT_INVERVAL_MS +# define DEFAULT_2P4G_REPORT_INVERVAL_MS 1 +#endif + +/* Default report interval value */ +#ifndef RETPORT_RETRY_COUNT +# define RETPORT_RETRY_COUNT 30 +#endif + +enum { + REPORT_TYPE_NONE, + REPORT_TYPE_KB, + REPORT_TYPE_NKRO, + REPORT_TYPE_CONSUMER, +}; + +typedef struct { + uint8_t type; + union { + report_keyboard_t keyboard; + report_nkro_t nkro; + uint16_t consumer; + }; +} report_buffer_t; + +void report_buffer_init(void); +bool report_buffer_enqueue(report_buffer_t *report); +bool report_buffer_dequeue(report_buffer_t *report); +bool report_buffer_is_empty(void); +void report_buffer_update_timer(void); +bool report_buffer_next_inverval(void); +void report_buffer_set_inverval(uint8_t interval); +uint8_t report_buffer_get_retry(void); +void report_buffer_set_retry(uint8_t times); +void report_buffer_task(void); diff --git a/keyboards/lemokey/common/wireless/rtc_timer.c b/keyboards/lemokey/common/wireless/rtc_timer.c new file mode 100644 index 00000000000..9a35b9bddb9 --- /dev/null +++ b/keyboards/lemokey/common/wireless/rtc_timer.c @@ -0,0 +1,43 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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 "hal.h" + +#if (HAL_USE_RTC) + +# include "rtc_timer.h" + +void rtc_timer_init(void) { + rtc_timer_clear(); +} + +void rtc_timer_clear(void) { + RTCDateTime tm = {0, 0, 0, 0, 0, 0}; + rtcSetTime(&RTCD1, &tm); +} + +uint32_t rtc_timer_read_ms(void) { + RTCDateTime tm; + rtcGetTime(&RTCD1, &tm); + + return tm.millisecond; +} + +uint32_t rtc_timer_elapsed_ms(uint32_t last) { + return TIMER_DIFF_32(rtc_timer_read_ms(), last); +} + +#endif diff --git a/keyboards/lemokey/common/wireless/rtc_timer.h b/keyboards/lemokey/common/wireless/rtc_timer.h new file mode 100644 index 00000000000..cf6dfb57202 --- /dev/null +++ b/keyboards/lemokey/common/wireless/rtc_timer.h @@ -0,0 +1,35 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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 "timer.h" +#include + +#define RTC_MAX_TIME (24 * 3600 * 1000) // Set to 1 day + +#ifdef __cplusplus +extern "C" { +#endif + +void rtc_timer_init(void); +void rtc_timer_clear(void); +uint32_t rtc_timer_read_ms(void); +uint32_t rtc_timer_elapsed_ms(uint32_t last); + +#ifdef __cplusplus +} +#endif diff --git a/keyboards/lemokey/common/wireless/transport.c b/keyboards/lemokey/common/wireless/transport.c new file mode 100644 index 00000000000..372abc8894b --- /dev/null +++ b/keyboards/lemokey/common/wireless/transport.c @@ -0,0 +1,272 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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" +#include "wireless.h" +#include "indicator.h" +#include "lpm.h" +#include "mousekey.h" +#if defined(PROTOCOL_CHIBIOS) +# include +#endif +#include "transport.h" +#include "lkbt51.h" + +#ifndef REINIT_LED_DRIVER +# define REINIT_LED_DRIVER 0 +#endif + +#if defined(PROTOCOL_CHIBIOS) +extern host_driver_t chibios_driver; +#endif +extern host_driver_t wireless_driver; +extern keymap_config_t keymap_config; +extern wt_func_t wireless_transport; + +static transport_t transport = TRANSPORT_NONE; + +#ifdef NKRO_ENABLE +nkro_t nkro = {false, false}; +#endif + +static void transport_changed(transport_t new_transport); + +__attribute__((weak)) void bt_transport_enable(bool enable) { + if (enable) { + // if (host_get_driver() != &wireless_driver) { + host_set_driver(&wireless_driver); + + /* Disconnect and reconnect to sync the wireless state + * TODO: query wireless state to sync + */ + wireless_disconnect(); + + uint32_t t = timer_read32(); + while (timer_elapsed32(t) < 50) { + wireless_transport.task(); + } + // wireless_connect(); + wireless_connect_ex(30, 0); + // TODO: Clear USB report + //} + } else { + indicator_stop(); + + if (wireless_get_state() == WT_CONNECTED && transport == TRANSPORT_BLUETOOTH) { + report_keyboard_t empty_report = {0}; + wireless_driver.send_keyboard(&empty_report); + } + } +} + +__attribute__((weak)) void p24g_transport_enable(bool enable) { + if (enable) { + // if (host_get_driver() != &wireless_driver) { + host_set_driver(&wireless_driver); + + /* Disconnect and reconnect to sync the wireless state + * TODO: query bluetooth state to sync + */ + wireless_disconnect(); + + uint32_t t = timer_read32(); + while (timer_elapsed32(t) < 50) { + wireless_transport.task(); + } + wireless_connect_ex(P24G_INDEX, 0); + // wireless_connect(); + // TODO: Clear USB report + //} + } else { + indicator_stop(); + + if (wireless_get_state() == WT_CONNECTED && transport == TRANSPORT_P2P4) { + report_keyboard_t empty_report = {0}; + wireless_driver.send_keyboard(&empty_report); + } + } +} + +__attribute__((weak)) void usb_power_connect(void) {} +__attribute__((weak)) void usb_power_disconnect(void) {} + +__attribute__((weak)) void usb_transport_enable(bool enable) { + if (enable) { + if (host_get_driver() != &chibios_driver) { +#if !defined(KEEP_USB_CONNECTION_IN_WIRELESS_MODE) + usb_power_connect(); + usb_start(&USBD1); +#endif + host_set_driver(&chibios_driver); + } + } else { + if (USB_DRIVER.state == USB_ACTIVE) { + report_keyboard_t empty_report = {0}; + chibios_driver.send_keyboard(&empty_report); + } + +#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) { + if (transport == TRANSPORT_USB || ((transport != TRANSPORT_USB) && wireless_get_state() == WT_CONNECTED)) clear_keyboard(); + + transport = new_transport; + + switch (transport) { + case TRANSPORT_USB: + usb_transport_enable(true); + bt_transport_enable(false); + wait_ms(5); + p24g_transport_enable(false); + wireless_disconnect(); + lpm_timer_stop(); +#ifdef NKRO_ENABLE +# if defined(WIRELESS_NKRO_ENABLE) + nkro.bluetooth = keymap_config.nkro; +# endif + keymap_config.nkro = nkro.usb; +#endif + break; + + case TRANSPORT_BLUETOOTH: + p24g_transport_enable(false); + wait_ms(1); + bt_transport_enable(true); + usb_transport_enable(false); + lpm_timer_reset(); +#if defined(NKRO_ENABLE) + nkro.usb = keymap_config.nkro; +# if defined(WIRELESS_NKRO_ENABLE) + keymap_config.nkro = nkro.bluetooth; +# else + keymap_config.nkro = FALSE; +# endif +#endif + break; + + case TRANSPORT_P2P4: + bt_transport_enable(false); + wait_ms(1); + p24g_transport_enable(true); + usb_transport_enable(false); + lpm_timer_reset(); +#if defined(NKRO_ENABLE) + nkro.usb = keymap_config.nkro; +# if defined(WIRELESS_NKRO_ENABLE) + keymap_config.nkro = nkro.bluetooth; +# else + keymap_config.nkro = FALSE; +# endif +#endif + break; + + default: + break; + } + + transport_changed(transport); + } +} + +transport_t get_transport(void) { + return transport; +} + +#if (REINIT_LED_DRIVER) +/* Changing transport may cause bronw-out reset of led driver + * withoug MCU reset, which lead backlight to not work, + * reinit the led driver workgound this issue */ +static void reinit_led_drvier(void) { + /* Wait circuit to discharge for a while */ + systime_t start = chVTGetSystemTime(); + while (chTimeI2MS(chVTTimeElapsedSinceX(start)) < 100) { + }; + +# ifdef LED_MATRIX_ENABLE + led_matrix_init(); +# endif +# ifdef RGB_MATRIX_ENABLE + rgb_matrix_init(); +# endif +} +#endif + +void transport_changed(transport_t new_transport) { + kc_printf("transport_changed %d\n\r", new_transport); + indicator_init(); + +#if (REINIT_LED_DRIVER) + reinit_led_drvier(); +#endif + +#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_TIMEOUT) +# if (RGB_MATRIX_TIMEOUT > 0) + rgb_matrix_disable_timeout_set(RGB_MATRIX_TIMEOUT_INFINITE); + rgb_matrix_disable_time_reset(); +# endif +#endif +#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_TIMEOUT) +# if (LED_MATRIX_TIMEOUT > 0) + led_matrix_disable_timeout_set(LED_MATRIX_TIMEOUT_INFINITE); + led_matrix_disable_time_reset(); +# endif +#endif +} + +void usb_remote_wakeup(void) { + if (USB_DRIVER.state == USB_SUSPENDED) { + 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 (suspend_wakeup_condition()) { + usbWakeupHost(&USB_DRIVER); + wait_ms(300); +#ifdef MOUSEKEY_ENABLE + // Wiggle to wakeup + mousekey_on(KC_MS_LEFT); + mousekey_send(); + wait_ms(10); + mousekey_on(KC_MS_RIGHT); + mousekey_send(); + wait_ms(10); + mousekey_off((KC_MS_RIGHT)); + mousekey_send(); +#else + set_mods(0x02); + send_keyboard_report(); + wait_ms(10); + del_mods(0x02); + send_keyboard_report(); +#endif + } + } + /* Woken up */ + // variables has been already cleared by the wakeup hook + send_keyboard_report(); +#ifdef MOUSEKEY_ENABLE + mousekey_send(); +#endif /* MOUSEKEY_ENABLE */ + usb_event_queue_task(); + } +} diff --git a/keyboards/lemokey/common/wireless/transport.h b/keyboards/lemokey/common/wireless/transport.h new file mode 100644 index 00000000000..2dad9c0cca8 --- /dev/null +++ b/keyboards/lemokey/common/wireless/transport.h @@ -0,0 +1,42 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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 + +typedef enum { + TRANSPORT_NONE, + TRANSPORT_USB = 0x01 << 0, + TRANSPORT_BLUETOOTH = 0x01 << 1, + TRANSPORT_P2P4 = 0x01 << 2, + TRANSPORT_MAX, +} transport_t; + +#ifdef NKRO_ENABLE +typedef struct { + bool usb : 1; + bool bluetooth : 1; +} nkro_t; +#endif + +#define TRANSPORT_WIRELESS (TRANSPORT_BLUETOOTH | TRANSPORT_P2P4) + +void set_transport(transport_t new_transport); +transport_t get_transport(void); + +void usb_power_connect(void); +void usb_power_disconnect(void); +void usb_transport_enable(bool enable); +void usb_remote_wakeup(void); diff --git a/keyboards/lemokey/common/wireless/wireless.c b/keyboards/lemokey/common/wireless/wireless.c new file mode 100644 index 00000000000..eddc5389070 --- /dev/null +++ b/keyboards/lemokey/common/wireless/wireless.c @@ -0,0 +1,558 @@ +/* Copyright 2022 @ lokher (https://www.keychron.com) + * + * 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" +#include "wireless.h" +#include "report_buffer.h" +#include "lpm.h" +#include "battery.h" +#include "indicator.h" +#include "transport.h" +#include "rtc_timer.h" +#include "lemokey_wireless_common.h" +#include "lemokey_task.h" + +extern uint8_t pairing_indication; +extern host_driver_t chibios_driver; +extern report_buffer_t kb_rpt; +extern uint32_t retry_time_buffer; +extern uint8_t retry; + +#ifdef NKRO_ENABLE +extern nkro_t nkro; +#endif + +static uint8_t host_index = 0; +static uint8_t led_state = 0; + +extern wt_func_t wireless_transport; +static wt_state_t wireless_state = WT_RESET; +static bool pincodeEntry = false; +uint8_t wireless_report_protocol = true; + +/* declarations */ +uint8_t wreless_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); +bool process_record_wireless(uint16_t keycode, keyrecord_t *record); + +/* host struct */ +host_driver_t wireless_driver = {wreless_keyboard_leds, wireless_send_keyboard, wireless_send_nkro, wireless_send_mouse, wireless_send_extra}; + +#define WT_EVENT_QUEUE_SIZE 16 +wireless_event_t wireless_event_queue[WT_EVENT_QUEUE_SIZE]; +uint8_t wireless_event_queue_head; +uint8_t wireless_event_queue_tail; + +void wireless_event_queue_init(void) { + // Initialise the event queue + memset(&wireless_event_queue, 0, sizeof(wireless_event_queue)); + wireless_event_queue_head = 0; + wireless_event_queue_tail = 0; +} + +bool wireless_event_enqueue(wireless_event_t event) { + uint8_t next = (wireless_event_queue_head + 1) % WT_EVENT_QUEUE_SIZE; + if (next == wireless_event_queue_tail) { + /* Override the first report */ + wireless_event_queue_tail = (wireless_event_queue_tail + 1) % WT_EVENT_QUEUE_SIZE; + } + wireless_event_queue[wireless_event_queue_head] = event; + wireless_event_queue_head = next; + return true; +} + +static inline bool wireless_event_dequeue(wireless_event_t *event) { + if (wireless_event_queue_head == wireless_event_queue_tail) { + return false; + } + *event = wireless_event_queue[wireless_event_queue_tail]; + wireless_event_queue_tail = (wireless_event_queue_tail + 1) % WT_EVENT_QUEUE_SIZE; + return true; +} + +/* + * Bluetooth init. + */ +void wireless_init(void) { + wireless_state = WT_INITIALIZED; + + wireless_event_queue_init(); +#ifndef DISABLE_REPORT_BUFFER + report_buffer_init(); +#endif + indicator_init(); +#ifdef BLUETOOTH_INT_INPUT_PIN + setPinInputHigh(BLUETOOTH_INT_INPUT_PIN); +#endif + + battery_init(); + lpm_init(); +#if HAL_USE_RTC + rtc_timer_init(); +#endif +#ifdef NKRO_ENABLE + keymap_config.raw = eeconfig_read_keymap(); + nkro.usb = keymap_config.nkro; +# ifdef WIRELESS_NKRO_ENABLE + nkro.bluetooth = keymap_config.nkro; +# endif +#endif +} + +/* + * Bluetooth trasponrt init. Bluetooth module driver shall use this function to register a callback + * to its implementation. + */ +void wireless_set_transport(wt_func_t *transport) { + if (transport) memcpy(&wireless_transport, transport, sizeof(wt_func_t)); +} + +/* + * Enter pairing with current host index + */ +void wireless_pairing(void) { + if (battery_is_critical_low()) return; + + wireless_pairing_ex(0, NULL); + wireless_state = WT_PARING; +} + +/* + * Enter pairing with specified host index and param + */ +void wireless_pairing_ex(uint8_t host_idx, void *param) { + kc_printf("wireless_pairing_ex %d\n\r", host_idx); + if (battery_is_critical_low()) return; + + if (wireless_transport.pairing_ex) wireless_transport.pairing_ex(host_idx, param); + wireless_state = WT_PARING; + + host_index = host_idx; +} + +/* + * Initiate connection request to paired host + */ +void wireless_connect(void) { + /* Work around empty report after wakeup, which leads to reconneect/disconnected loop */ + if (battery_is_critical_low() || timer_read32() == 0) return; + + if (wireless_state == WT_RECONNECTING && !indicator_is_running()) { + indicator_set(wireless_state, host_index); + } + wireless_transport.connect_ex(0, 0); + wireless_state = WT_RECONNECTING; +} + +/* + * Initiate connection request to paired host with argument + */ +void wireless_connect_ex(uint8_t host_idx, uint16_t timeout) { + kc_printf("wireless_connect_ex %d\n\r", host_idx); + if (battery_is_critical_low()) return; + + if (host_idx != 0) { + /* Do nothing when trying to connect to current connected host*/ + if (host_index == host_idx && wireless_state == WT_CONNECTED) return; + + host_index = host_idx; + led_state = 0; + } + wireless_transport.connect_ex(host_idx, timeout); + wireless_state = WT_RECONNECTING; +} + +/* Initiate a disconnection */ +void wireless_disconnect(void) { + kc_printf("wireless_disconnect\n\r"); + if (wireless_transport.disconnect) wireless_transport.disconnect(); +} + +/* Called when the BT device is reset. */ +static void wireless_enter_reset(uint8_t reason) { + kc_printf("wireless_enter_reset\n\r"); + wireless_state = WT_RESET; + wireless_enter_reset_kb(reason); +} + +/* Enters discoverable state. Upon entering this state we perform the following actions: + * - change state to WT_PARING + * - set pairing indication + */ +static void wireless_enter_discoverable(uint8_t host_idx) { + kc_printf("wireless_enter_discoverable: %d\n\r", host_idx); + host_index = host_idx; + + wireless_state = WT_PARING; + indicator_set(wireless_state, host_idx); + wireless_enter_discoverable_kb(host_idx); +} + +/* + * Enters reconnecting state. Upon entering this state we perform the following actions: + * - change state to RECONNECTING + * - set reconnect indication + */ +static void wireless_enter_reconnecting(uint8_t host_idx) { + host_index = host_idx; + + kc_printf("wireless_reconnecting %d\n\r", host_idx); + wireless_state = WT_RECONNECTING; + indicator_set(wireless_state, host_idx); + wireless_enter_reconnecting_kb(host_idx); +} + +/* Enters connected state. Upon entering this state we perform the following actions: + * - change state to CONNECTED + * - set connected indication + * - enable NKRO if it is support + */ +static void wireless_enter_connected(uint8_t host_idx) { + kc_printf("wireless_connected %d\n\r", host_idx); + + wireless_state = WT_CONNECTED; + indicator_set(wireless_state, host_idx); + host_index = host_idx; + + clear_keyboard(); + + /* Enable NKRO since it may be disabled in pin code entry */ +#if defined(NKRO_ENABLE) && defined(WIRELESS_NKRO_ENABLE) + keymap_config.nkro = nkro.bluetooth; +#else + keymap_config.nkro = false; +#endif + + wireless_enter_connected_kb(host_idx); +#ifdef BAT_LOW_LED_PIN + if (battery_is_empty()) { + indicator_battery_low_enable(true); + } +#endif + if (wireless_transport.update_bat_level) wireless_transport.update_bat_level(battery_get_percentage()); +} + +/* Enters disconnected state. Upon entering this state we perform the following actions: + * - change state to DISCONNECTED + * - set disconnected indication + */ +static void wireless_enter_disconnected(uint8_t host_idx) { + kc_printf("wireless_disconnected %d\n\r", host_idx); + + uint8_t previous_state = wireless_state; + led_state = 0; + led_update_kb((led_t)led_state); + + wireless_state = WT_DISCONNECTED; + + if (previous_state == WT_CONNECTED) { + lpm_timer_reset(); + indicator_set(WT_SUSPEND, host_idx); + } else + indicator_set(wireless_state, host_idx); + +#ifndef DISABLE_REPORT_BUFFER + report_buffer_init(); +#endif + retry = 0; + wireless_enter_disconnected_kb(host_idx); +#ifdef BAT_LOW_LED_PIN + indicator_battery_low_enable(false); +#endif +#if defined(LOW_BAT_IND_INDEX) + indicator_battery_low_backlit_enable(false); +#endif +} + +/* Enter pin code entry state. */ +static void wireless_enter_bluetooth_pin_code_entry(void) { +#if defined(NKRO_ENABLE) + keymap_config.nkro = FALSE; +#endif + pincodeEntry = true; + wireless_enter_bluetooth_pin_code_entry_kb(); +} + +/* Exit pin code entry state. */ +static void wireless_exit_bluetooth_pin_code_entry(void) { +#if defined(NKRO_ENABLE) + keymap_config.nkro = true; +#endif + pincodeEntry = false; + wireless_exit_bluetooth_pin_code_entry_kb(); +} + +/* Enters disconnected state. Upon entering this state we perform the following actions: + * - change state to DISCONNECTED + * - set disconnected indication + */ +static void wireless_enter_sleep(void) { + kc_printf("wireless_enter_sleep %d\n\r", wireless_state); + + led_state = 0; + if (wireless_state == WT_PARING) { + wireless_state = WT_SUSPEND; + kc_printf("WT_SUSPEND\n\r"); + + wireless_enter_sleep_kb(); + indicator_set(wireless_state, 0); +#ifdef BAT_LOW_LED_PIN + indicator_battery_low_enable(false); +#endif +#if defined(LOW_BAT_IND_INDEX) + indicator_battery_low_backlit_enable(false); +#endif + } +} + +__attribute__((weak)) void wireless_enter_reset_kb(uint8_t reason) {} +__attribute__((weak)) void wireless_enter_discoverable_kb(uint8_t host_idx) {} +__attribute__((weak)) void wireless_enter_reconnecting_kb(uint8_t host_idx) {} +__attribute__((weak)) void wireless_enter_connected_kb(uint8_t host_idx) {} +__attribute__((weak)) void wireless_enter_disconnected_kb(uint8_t host_idx) {} +__attribute__((weak)) void wireless_enter_bluetooth_pin_code_entry_kb(void) {} +__attribute__((weak)) void wireless_exit_bluetooth_pin_code_entry_kb(void) {} +__attribute__((weak)) void wireless_enter_sleep_kb(void) {} + +/* */ +static void wireless_hid_set_protocol(bool report_protocol) { + wireless_report_protocol = false; +} + +uint8_t wreless_keyboard_leds(void) { + if (wireless_state == WT_CONNECTED) { + return led_state; + } + + return 0; +} + +extern keymap_config_t keymap_config; + +void wireless_send_keyboard(report_keyboard_t *report) { + if (battery_is_critical_low()) return; + + if (wireless_state == WT_PARING && !pincodeEntry) return; + + if (wireless_state == WT_CONNECTED || (wireless_state == WT_PARING && pincodeEntry)) { + if (wireless_transport.send_keyboard) { +#ifndef DISABLE_REPORT_BUFFER + report_buffer_t report_buffer; + report_buffer.type = REPORT_TYPE_KB; + memcpy(&report_buffer.keyboard, report, sizeof(report_keyboard_t)); + report_buffer_enqueue(&report_buffer); +#else + wireless_transport.send_keyboard(&report->mods); +#endif + } + } else if (wireless_state != WT_RESET) { + wireless_connect(); + } +} + +void wireless_send_nkro(report_nkro_t *report) { + if (battery_is_critical_low()) return; + + if (wireless_state == WT_PARING && !pincodeEntry) return; + + if (wireless_state == WT_CONNECTED || (wireless_state == WT_PARING && pincodeEntry)) { + if (wireless_transport.send_nkro) { +#ifndef DISABLE_REPORT_BUFFER + report_buffer_t report_buffer; + report_buffer.type = REPORT_TYPE_NKRO; + memcpy(&report_buffer.nkro, report, sizeof(report_nkro_t)); + report_buffer_enqueue(&report_buffer); +#else + wireless_transport.send_nkro(&report->mods); +#endif + } + } else if (wireless_state != WT_RESET) { + wireless_connect(); + } +} + +void wireless_send_mouse(report_mouse_t *report) { + if (battery_is_critical_low()) return; + + if (wireless_state == WT_CONNECTED) { + if (wireless_transport.send_mouse) wireless_transport.send_mouse((uint8_t *)report); + } else if (wireless_state != WT_RESET) { + wireless_connect(); + } +} + +void wireless_send_system(uint16_t data) { + if (wireless_state == WT_CONNECTED) { + if (wireless_transport.send_system) wireless_transport.send_system(data); + } else if (wireless_state != WT_RESET) { + wireless_connect(); + } +} + +void wireless_send_consumer(uint16_t data) { + if (wireless_state == WT_CONNECTED) { +#ifndef DISABLE_REPORT_BUFFER + if (report_buffer_is_empty() && report_buffer_next_inverval()) { + if (wireless_transport.send_consumer) wireless_transport.send_consumer(data); + report_buffer_update_timer(); + } else { + report_buffer_t report_buffer; + report_buffer.type = REPORT_TYPE_CONSUMER; + report_buffer.consumer = data; + report_buffer_enqueue(&report_buffer); + } +#else + if (wireless_transport.send_consumer) wireless_transport.send_consumer(data); +#endif + } else if (wireless_state != WT_RESET) { + wireless_connect(); + } +} + +void wireless_send_extra(report_extra_t *report) { + if (battery_is_critical_low()) return; + + if (report->report_id == REPORT_ID_SYSTEM) { + wireless_send_system(report->usage); + } else if (report->report_id == REPORT_ID_CONSUMER) { + wireless_send_consumer(report->usage); + } +} + +void wireless_low_battery_shutdown(void) { +#ifdef BAT_LOW_LED_PIN + indicator_battery_low_enable(false); +#endif +#if defined(LOW_BAT_IND_INDEX) + indicator_battery_low_backlit_enable(false); +#endif + + report_buffer_init(); + clear_keyboard(); // + wait_ms(50); // wait a while for bt module to free buffer by sending report + + // Release all keys by sending empty reports + if (keymap_config.nkro) { + report_nkro_t empty_nkro_report; + memset(&empty_nkro_report, 0, sizeof(empty_nkro_report)); + wireless_transport.send_nkro(&empty_nkro_report.mods); + } else { + report_keyboard_t empty_report; + memset(&empty_report, 0, sizeof(empty_report)); + wireless_transport.send_keyboard(&empty_report.mods); + } + wait_ms(10); + wireless_transport.send_consumer(0); + wait_ms(10); + report_mouse_t empty_mouse_report; + memset(&empty_mouse_report, 0, sizeof(empty_mouse_report)); + wireless_transport.send_mouse((uint8_t *)&empty_mouse_report); + wait_ms(300); // Wait for bt module to send all buffered report + + wireless_disconnect(); +} + +void wireless_event_task(void) { + wireless_event_t event; + while (wireless_event_dequeue(&event)) { + switch (event.evt_type) { + case EVT_RESET: + wireless_enter_reset(event.params.reason); + break; + case EVT_CONNECTED: + wireless_enter_connected(event.params.hostIndex); + break; + case EVT_DISCOVERABLE: + wireless_enter_discoverable(event.params.hostIndex); + break; + case EVT_RECONNECTING: + wireless_enter_reconnecting(event.params.hostIndex); + break; + case EVT_DISCONNECTED: + wireless_enter_disconnected(event.params.hostIndex); + break; + case EVT_BT_PINCODE_ENTRY: + wireless_enter_bluetooth_pin_code_entry(); + break; + case EVT_EXIT_BT_PINCODE_ENTRY: + wireless_exit_bluetooth_pin_code_entry(); + break; + case EVT_SLEEP: + wireless_enter_sleep(); + break; + case EVT_HID_INDICATOR: + led_state = event.params.led; + break; + case EVT_HID_SET_PROTOCOL: + wireless_hid_set_protocol(event.params.protocol); + break; + case EVT_CONECTION_INTERVAL: + report_buffer_set_inverval(event.params.interval); + break; + default: + break; + } + } +} + +void wireless_task(void) { + wireless_transport.task(); + wireless_event_task(); +#ifndef DISABLE_REPORT_BUFFER + report_buffer_task(); +#endif + indicator_task(); + lemokey_wireless_common_task(); + battery_task(); + lpm_task(); +} + +void send_string_task(void) { + if ((get_transport() & TRANSPORT_WIRELESS) && wireless_get_state() == WT_CONNECTED) { + wireless_transport.task(); +#ifndef DISABLE_REPORT_BUFFER + report_buffer_task(); +#endif + } +} + +wt_state_t wireless_get_state(void) { + return wireless_state; +}; + +bool process_record_wireless(uint16_t keycode, keyrecord_t *record) { + if (get_transport() & TRANSPORT_WIRELESS) { + lpm_timer_reset(); + +#if defined(BAT_LOW_LED_PIN) || defined(LOW_BAT_IND_INDEX) + if (battery_is_empty() && wireless_get_state() == WT_CONNECTED && record->event.pressed) { +# if defined(BAT_LOW_LED_PIN) + indicator_battery_low_enable(true); +# endif +# if defined(LOW_BAT_IND_INDEX) + indicator_battery_low_backlit_enable(true); +# endif + } +#endif + } + + if (!process_record_lemokey_wireless(keycode, record)) return false; + + return true; +} diff --git a/keyboards/lemokey/common/wireless/wireless.h b/keyboards/lemokey/common/wireless/wireless.h new file mode 100644 index 00000000000..adfea6e77a9 --- /dev/null +++ b/keyboards/lemokey/common/wireless/wireless.h @@ -0,0 +1,101 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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 "wireless_event_type.h" +#include "action.h" + +#ifdef KC_DEBUG +# define kc_printf dprintf +#else +# define kc_printf(format, ...) +#endif + +/* Low power mode */ +#ifndef LOW_POWER_MODE +# define LOW_POWER_MODE PM_STOP +#endif + +/* Wake pin used for blueooth module/controller to wake up MCU in low power mode*/ +#ifndef BLUETOOTH_INT_INPUT_PIN +# define WAKE_PIN A5 +#endif + +// clang-format off +/* Type of an enumeration of the possible wireless transport state.*/ +typedef enum { + WT_RESET, + WT_INITIALIZED, // 1 + WT_DISCONNECTED, // 2 + WT_CONNECTED, // 3 + WT_PARING, // 4 + WT_RECONNECTING, // 5 + WT_SUSPEND +} wt_state_t; + +//extern event_listener_t wireless_driver; + +typedef struct { + void (*init)(bool); + void (*connect_ex)(uint8_t, uint16_t); + void (*pairing_ex)(uint8_t, void *); + void (*disconnect)(void); + void (*send_keyboard)(uint8_t *); + void (*send_nkro)(uint8_t *); + void (*send_consumer)(uint16_t); + void (*send_system)(uint16_t); + void (*send_mouse)(uint8_t *); + void (*update_bat_level)(uint8_t); + void (*task)(void); +} wt_func_t; +// clang-format on + +extern void register_wt_tasks(void); + +void wireless_init(void); +void wireless_set_transport(wt_func_t *transport); +void wireless(void); + +bool wireless_event_enqueue(wireless_event_t event); + +void wireless_connect(void); +void wireless_connect_ex(uint8_t host_idx, uint16_t timeout); +void wireless_disconnect(void); + +void wireless_pairing(void); +void wireless_pairing_ex(uint8_t host_idx, void *param); +// bool bluetooth_is_activated(void); + +void wireless_enter_reset_kb(uint8_t reason); +void wireless_enter_discoverable_kb(uint8_t host_idx); +void wireless_enter_reconnecting_kb(uint8_t host_idx); +void wireless_enter_connected_kb(uint8_t host_idx); +void wireless_enter_disconnected_kb(uint8_t host_idx); +void wireless_enter_bluetooth_pin_code_entry_kb(void); +void wireless_exit_bluetooth_pin_code_entry_kb(void); +void wireless_enter_sleep_kb(void); + +void wireless_task(void); +void wireless_pre_task(void); +void wireless_post_task(void); +void send_string_task(void); + +wt_state_t wireless_get_state(void); + +void wireless_low_battery_shutdown(void); + +bool process_record_wireless(uint16_t keycode, keyrecord_t *record); diff --git a/keyboards/lemokey/common/wireless/wireless.mk b/keyboards/lemokey/common/wireless/wireless.mk new file mode 100644 index 00000000000..2fe1b536214 --- /dev/null +++ b/keyboards/lemokey/common/wireless/wireless.mk @@ -0,0 +1,21 @@ +OPT_DEFS += -DLK_WIRELESS_ENABLE +OPT_DEFS += -DNO_USB_STARTUP_CHECK +OPT_DEFS += -DCORTEX_ENABLE_WFI_IDLE=TRUE + +WIRELESS_DIR = common/wireless +SRC += \ + $(WIRELESS_DIR)/wireless.c \ + $(WIRELESS_DIR)/report_buffer.c \ + $(WIRELESS_DIR)/lkbt51.c \ + $(WIRELESS_DIR)/indicator.c \ + $(WIRELESS_DIR)/wireless_main.c \ + $(WIRELESS_DIR)/transport.c \ + $(WIRELESS_DIR)/lpm.c \ + $(WIRELESS_DIR)/lpm_stm32f401.c \ + $(WIRELESS_DIR)/battery.c \ + $(WIRELESS_DIR)/bat_level_animation.c \ + $(WIRELESS_DIR)/rtc_timer.c \ + $(WIRELESS_DIR)/lemokey_wireless_common.c + +VPATH += $(TOP_DIR)/keyboards/lemokey/$(WIRELESS_DIR) + diff --git a/keyboards/lemokey/common/wireless/wireless_config.h b/keyboards/lemokey/common/wireless/wireless_config.h new file mode 100644 index 00000000000..e55ad242909 --- /dev/null +++ b/keyboards/lemokey/common/wireless/wireless_config.h @@ -0,0 +1,36 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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 "config.h" + +// +#ifndef BT_HOST_DEVICES_COUNT +# define BT_HOST_DEVICES_COUNT 3 +#endif + +#define P2P4G_HOST_DEVICES_COUNT 1 + +// Uint: Second +#ifndef DISCONNECTED_BACKLIGHT_OFF_DELAY_TIME +# define DISCONNECTED_BACKLIGHT_OFF_DELAY_TIME 40 +#endif + +// Uint: Second, the timer restarts on key activities. +#ifndef CONNECTED_BACKLIGHT_OFF_DELAY_TIME +# define CONNECTED_BACKLIGHT_OFF_DELAY_TIME 600 +#endif diff --git a/keyboards/lemokey/common/wireless/wireless_event_type.h b/keyboards/lemokey/common/wireless/wireless_event_type.h new file mode 100644 index 00000000000..e40f002ed3b --- /dev/null +++ b/keyboards/lemokey/common/wireless/wireless_event_type.h @@ -0,0 +1,44 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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 + +/* Type of an enumeration of the possible wireless events.*/ +typedef enum { + EVT_NONE = 0, + EVT_RESET, + EVT_DISCOVERABLE, + EVT_RECONNECTING, + EVT_CONNECTED, + EVT_DISCONNECTED, + EVT_BT_PINCODE_ENTRY, + EVT_EXIT_BT_PINCODE_ENTRY, + EVT_SLEEP, + EVT_HID_SET_PROTOCOL, + EVT_HID_INDICATOR, + EVT_CONECTION_INTERVAL, +} event_type_t; + +typedef struct { + event_type_t evt_type; /*The type of the event. */ + union { + uint8_t reason; /* Parameters to WT_RESET event */ + uint8_t hostIndex; /* Parameters to connection event from EVT_DISCOVERABLE to EVT_DISCONECTED */ + uint8_t led; /* Parameters to EVT_HID_INDICATOR event */ + uint8_t protocol; /* Parameters to EVT_HID_SET_PROTOCOL event */ + uint8_t interval; /* Parameters to EVT_CONECTION_INTERVAL event */ + } params; +} wireless_event_t; diff --git a/keyboards/lemokey/common/wireless/wireless_main.c b/keyboards/lemokey/common/wireless/wireless_main.c new file mode 100644 index 00000000000..62cc1e5756f --- /dev/null +++ b/keyboards/lemokey/common/wireless/wireless_main.c @@ -0,0 +1,36 @@ +/* Copyright 2023 @ lokher (https://www.keychron.com) + * + * 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" +#include "wireless.h" +#include "transport.h" +#include "factory_test.h" +#include "lemokey_task.h" + +__attribute__((weak)) void wireless_pre_task(void) {} +__attribute__((weak)) void wireless_post_task(void) {} + +bool wireless_tasks(void) { + wireless_pre_task(); + wireless_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(); + return true; +}