From 39161b9ee793f4c62836b209ec877acda457b88d Mon Sep 17 00:00:00 2001 From: HorrorTroll Date: Thu, 21 Nov 2024 13:28:28 +0700 Subject: [PATCH 1/2] Added MCU support for ArteryTek AT32F415 (#23445) --- builddefs/common_features.mk | 2 +- data/schemas/keyboard.jsonschema | 2 + docs/compatible_microcontrollers.md | 4 + docs/driver_installation_zadig.md | 1 + docs/flashing.md | 33 +++ .../handwired/onekey/at_start_f415/board.h | 10 + .../handwired/onekey/at_start_f415/config.h | 10 + .../handwired/onekey/at_start_f415/halconf.h | 13 + .../onekey/at_start_f415/keyboard.json | 20 ++ .../handwired/onekey/at_start_f415/mcuconf.h | 16 ++ .../handwired/onekey/at_start_f415/readme.md | 3 + lib/python/qmk/constants.py | 4 +- .../boards/GENERIC_AT32_F415XX/board/board.c | 101 ++++++++ .../boards/GENERIC_AT32_F415XX/board/board.h | 207 +++++++++++++++ .../boards/GENERIC_AT32_F415XX/board/board.mk | 9 + .../GENERIC_AT32_F415XX/configs/config.h | 13 + .../GENERIC_AT32_F415XX/configs/mcuconf.h | 236 ++++++++++++++++++ platforms/chibios/bootloader.mk | 9 + platforms/chibios/bootloaders/at32_dfu.c | 83 ++++++ platforms/chibios/chibios_config.h | 13 + platforms/chibios/drivers/analog.c | 23 +- platforms/chibios/drivers/serial_usart.c | 15 +- platforms/chibios/drivers/serial_usart.h | 99 +++++--- platforms/chibios/drivers/spi_master.c | 59 +++++ .../drivers/wear_leveling/wear_leveling_efl.c | 2 +- .../wear_leveling/wear_leveling_efl_config.h | 2 + platforms/chibios/drivers/ws2812_bitbang.c | 2 +- platforms/chibios/drivers/ws2812_pwm.c | 26 ++ platforms/chibios/drivers/ws2812_spi.c | 71 ++++-- platforms/chibios/flash.mk | 2 + platforms/chibios/mcu_selection.mk | 34 +++ platforms/chibios/platform.mk | 4 + 32 files changed, 1065 insertions(+), 63 deletions(-) create mode 100644 keyboards/handwired/onekey/at_start_f415/board.h create mode 100644 keyboards/handwired/onekey/at_start_f415/config.h create mode 100644 keyboards/handwired/onekey/at_start_f415/halconf.h create mode 100644 keyboards/handwired/onekey/at_start_f415/keyboard.json create mode 100644 keyboards/handwired/onekey/at_start_f415/mcuconf.h create mode 100644 keyboards/handwired/onekey/at_start_f415/readme.md create mode 100644 platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.c create mode 100644 platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.h create mode 100644 platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.mk create mode 100644 platforms/chibios/boards/GENERIC_AT32_F415XX/configs/config.h create mode 100644 platforms/chibios/boards/GENERIC_AT32_F415XX/configs/mcuconf.h create mode 100644 platforms/chibios/bootloaders/at32_dfu.c diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index f5b01de015d..86ced309029 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -215,7 +215,7 @@ else COMMON_VPATH += $(PLATFORM_PATH)/$(PLATFORM_KEY)/$(DRIVER_DIR)/flash COMMON_VPATH += $(DRIVER_PATH)/flash SRC += eeprom_driver.c eeprom_legacy_emulated_flash.c legacy_flash_ops.c - else ifneq ($(filter $(MCU_SERIES),STM32F1xx STM32F3xx STM32F4xx STM32L4xx STM32G4xx WB32F3G71xx WB32FQ95xx GD32VF103),) + else ifneq ($(filter $(MCU_SERIES),STM32F1xx STM32F3xx STM32F4xx STM32L4xx STM32G4xx WB32F3G71xx WB32FQ95xx AT32F415 GD32VF103),) # Wear-leveling EEPROM implementation, backed by MCU flash OPT_DEFS += -DEEPROM_DRIVER -DEEPROM_WEAR_LEVELING SRC += eeprom_driver.c eeprom_wear_leveling.c diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index 72a33f55ad1..9f1f6dd74a5 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema @@ -92,6 +92,7 @@ "GD32VF103", "WB32F3G71", "WB32FQ95", + "AT32F415", "atmega16u2", "atmega32u2", "atmega16u4", @@ -216,6 +217,7 @@ "type": "string", "enum": [ "apm32-dfu", + "at32-dfu", "atmel-dfu", "bootloadhid", "caterina", diff --git a/docs/compatible_microcontrollers.md b/docs/compatible_microcontrollers.md index e2d27e06f2f..f148c39191e 100644 --- a/docs/compatible_microcontrollers.md +++ b/docs/compatible_microcontrollers.md @@ -57,6 +57,10 @@ You can also use any ARM chip with USB that [ChibiOS](https://www.chibios.org) s * [WB32F3G71xx](http://www.westberrytech.com) * [WB32FQ95xx](http://www.westberrytech.com) +### Artery (AT32) + + * [AT32F415](https://www.arterychip.com/en/product/AT32F415.jsp) + ### NXP (Kinetis) * [MKL26Z64](https://www.nxp.com/products/processors-and-microcontrollers/arm-microcontrollers/general-purpose-mcus/kl-series-cortex-m0-plus/kinetis-kl2x-72-96-mhz-usb-ultra-low-power-microcontrollers-mcus-based-on-arm-cortex-m0-plus-core:KL2x) diff --git a/docs/driver_installation_zadig.md b/docs/driver_installation_zadig.md index 9743c0adc2f..1a5bd1cc346 100644 --- a/docs/driver_installation_zadig.md +++ b/docs/driver_installation_zadig.md @@ -98,6 +98,7 @@ The device name here is the name that appears in Zadig, and may not be what the |`bootloadhid` |HIDBoot |`16C0:05DF` |HidUsb | |`usbasploader`|USBasp |`16C0:05DC` |libusbK| |`apm32-dfu` |APM32 DFU ISP Mode |`314B:0106` |WinUSB | +|`at32-dfu` |AT32 Bootloader DFU |`2E3C:DF11` |WinUSB | |`stm32-dfu` |STM32 BOOTLOADER |`0483:DF11` |WinUSB | |`gd32v-dfu` |GD32V BOOTLOADER |`28E9:0189` |WinUSB | |`kiibohd` |Kiibohd DFU Bootloader |`1C11:B007` |WinUSB | diff --git a/docs/flashing.md b/docs/flashing.md index 2afb8588602..29dc780aaf0 100644 --- a/docs/flashing.md +++ b/docs/flashing.md @@ -345,6 +345,39 @@ Flashing sequence: 3. Flash a .bin file 4. Reset the device into application mode (may be done automatically) +## AT32 DFU + +All AT32 MCUs come preloaded with a factory bootloader that cannot be modified nor deleted. + +To ensure compatibility with the AT32-DFU bootloader, make sure this block is present in your `rules.mk`: + +```make +# Bootloader selection +BOOTLOADER = at32-dfu +``` + +Compatible flashers: + +* [dfu-util](https://dfu-util.sourceforge.net/) / `:dfu-util` target in QMK (recommended command line) + ``` + dfu-util -a 0 -d 2E3C:DF11 -s 0x8000000:leave -D + ``` + +Flashing sequence: + +1. Enter the bootloader using any of the following methods: + * Tap the `QK_BOOT` keycode + * If a reset circuit is present, tap the `RESET` button on the PCB; some boards may also have a toggle switch that must be flipped + * Otherwise, you need to bridge `BOOT0` to VCC (via `BOOT0` button or jumper), short `RESET` to GND (via `RESET` button or jumper), and then let go of the `BOOT0` bridge +2. Wait for the OS to detect the device +3. Flash a .bin file +4. Reset the device into application mode (may be done automatically) + +### `make` Targets + +* `:dfu-util`: Waits until an AT32 bootloader device is available, and then flashes the firmware. +* `:dfu-util-split-left` and `:dfu-util-split-right`: Flashes the firmware as with `:dfu-util`, but also sets the handedness setting in EEPROM. + ## tinyuf2 Keyboards may opt into supporting the tinyuf2 bootloader. This is currently only supported on F303/F401/F411. diff --git a/keyboards/handwired/onekey/at_start_f415/board.h b/keyboards/handwired/onekey/at_start_f415/board.h new file mode 100644 index 00000000000..f4adc2d4183 --- /dev/null +++ b/keyboards/handwired/onekey/at_start_f415/board.h @@ -0,0 +1,10 @@ +// Copyright 2023-2024 HorrorTroll +// Copyright 2023-2024 Zhaqian +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include_next + +#undef AT32F415KB +#define AT32F415RC diff --git a/keyboards/handwired/onekey/at_start_f415/config.h b/keyboards/handwired/onekey/at_start_f415/config.h new file mode 100644 index 00000000000..266e45dd004 --- /dev/null +++ b/keyboards/handwired/onekey/at_start_f415/config.h @@ -0,0 +1,10 @@ +// Copyright 2023-2024 HorrorTroll +// Copyright 2023-2024 Zhaqian +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#define ADC_PIN A0 + +#define BACKLIGHT_PWM_DRIVER PWMD5 +#define BACKLIGHT_PWM_CHANNEL 1 diff --git a/keyboards/handwired/onekey/at_start_f415/halconf.h b/keyboards/handwired/onekey/at_start_f415/halconf.h new file mode 100644 index 00000000000..1423633d15f --- /dev/null +++ b/keyboards/handwired/onekey/at_start_f415/halconf.h @@ -0,0 +1,13 @@ +// Copyright 2023-2024 HorrorTroll +// Copyright 2023-2024 Zhaqian +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#define HAL_USE_ADC TRUE + +#define HAL_USE_I2C TRUE + +#define HAL_USE_PWM TRUE + +#include_next diff --git a/keyboards/handwired/onekey/at_start_f415/keyboard.json b/keyboards/handwired/onekey/at_start_f415/keyboard.json new file mode 100644 index 00000000000..f598923a6a5 --- /dev/null +++ b/keyboards/handwired/onekey/at_start_f415/keyboard.json @@ -0,0 +1,20 @@ +{ + "keyboard_name": "Onekey AT-START-F415", + "processor": "AT32F415", + "bootloader": "at32-dfu", + "usb": { + "shared_endpoint": { + "keyboard": true + } + }, + "matrix_pins": { + "cols": ["B3"], + "rows": ["B4"] + }, + "backlight": { + "pin": "A0" + }, + "ws2812": { + "pin": "B0" + } +} diff --git a/keyboards/handwired/onekey/at_start_f415/mcuconf.h b/keyboards/handwired/onekey/at_start_f415/mcuconf.h new file mode 100644 index 00000000000..9bc11d507cc --- /dev/null +++ b/keyboards/handwired/onekey/at_start_f415/mcuconf.h @@ -0,0 +1,16 @@ +// Copyright 2023-2024 HorrorTroll +// Copyright 2023-2024 Zhaqian +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include_next + +#undef AT32_ADC_USE_ADC1 +#define AT32_ADC_USE_ADC1 TRUE + +#undef AT32_I2C_USE_I2C1 +#define AT32_I2C_USE_I2C1 TRUE + +#undef AT32_PWM_USE_TMR5 +#define AT32_PWM_USE_TMR5 TRUE diff --git a/keyboards/handwired/onekey/at_start_f415/readme.md b/keyboards/handwired/onekey/at_start_f415/readme.md new file mode 100644 index 00000000000..499ef5fb169 --- /dev/null +++ b/keyboards/handwired/onekey/at_start_f415/readme.md @@ -0,0 +1,3 @@ +# Artery AT-START-F415 Board Onekey + +To trigger keypress, short together pins *B3* and *B4*. diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index b6f46180b29..e055d3fbc95 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -22,7 +22,7 @@ QMK_FIRMWARE_UPSTREAM = 'qmk/qmk_firmware' MAX_KEYBOARD_SUBFOLDERS = 5 # Supported processor types -CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK64FX512', 'MK66FX1M0', 'RP2040', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32H723', 'STM32H733', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95' +CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK64FX512', 'MK66FX1M0', 'RP2040', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32H723', 'STM32H733', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95', 'AT32F415' LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' @@ -55,6 +55,7 @@ MCU2BOOTLOADER = { "GD32VF103": "gd32v-dfu", "WB32F3G71": "wb32-dfu", "WB32FQ95": "wb32-dfu", + "AT32F415": "at32-dfu", "atmega16u2": "atmel-dfu", "atmega32u2": "atmel-dfu", "atmega16u4": "atmel-dfu", @@ -93,6 +94,7 @@ BOOTLOADER_VIDS_PIDS = { 'apm32-dfu': {("314b", "0106")}, 'gd32v-dfu': {("28e9", "0189")}, 'wb32-dfu': {("342d", "dfa0")}, + 'at32-dfu': {("2e3c", "df11")}, 'bootloadhid': {("16c0", "05df")}, 'usbasploader': {("16c0", "05dc")}, 'usbtinyisp': {("1782", "0c9f")}, diff --git a/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.c b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.c new file mode 100644 index 00000000000..28cf7c18e01 --- /dev/null +++ b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.c @@ -0,0 +1,101 @@ +/* + ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio + ChibiOS - Copyright (C) 2023..2024 HorrorTroll + ChibiOS - Copyright (C) 2023..2024 Zhaqian + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "hal.h" + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + +/** + * @brief PAL setup. + * @details Digital I/O ports static configuration as defined in @p board.h. + * This variable is used by the HAL when initializing the PAL driver. + */ +#if HAL_USE_PAL || defined(__DOXYGEN__) +const PALConfig pal_default_config = +{ + {VAL_GPIOAODT, VAL_GPIOACFGLR, VAL_GPIOACFGHR}, + {VAL_GPIOBODT, VAL_GPIOBCFGLR, VAL_GPIOBCFGHR}, +#if AT32_HAS_GPIOC + {VAL_GPIOCODT, VAL_GPIOCCFGLR, VAL_GPIOCCFGHR}, +#endif + {VAL_GPIODODT, VAL_GPIODCFGLR, VAL_GPIODCFGHR}, +#if AT32_HAS_GPIOF + {VAL_GPIOFODT, VAL_GPIOFCFGLR, VAL_GPIOFCFGHR}, +#endif +}; +#endif + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver interrupt handlers. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Early initialization code. + * @details System clocks are initialized before everything else. + */ +void __early_init(void) { + at32_clock_init(); +} + +#if HAL_USE_SDC || defined(__DOXYGEN__) +/** + * @brief SDC card detection. + */ +bool sdc_lld_is_card_inserted(SDCDriver *sdcp) { + static bool last_status = false; + + if (blkIsTransferring(sdcp)) + return last_status; + return last_status = (bool)palReadPad(GPIOC, GPIOC_PIN11); +} + +/** + * @brief SDC card write protection detection. + */ +bool sdc_lld_is_write_protected(SDCDriver *sdcp) { + + (void)sdcp; + return false; +} +#endif /* HAL_USE_SDC */ + +/** + * @brief Board-specific initialization code. + * @note You can add your board-specific code here. + */ +void boardInit(void) { + IOMUX->REMAP |= IOMUX_REMAP_SWJTAG_MUX_JTAGDIS; +} diff --git a/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.h b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.h new file mode 100644 index 00000000000..c3ade198bae --- /dev/null +++ b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.h @@ -0,0 +1,207 @@ +/* + ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio + ChibiOS - Copyright (C) 2023..2024 HorrorTroll + ChibiOS - Copyright (C) 2023..2024 Zhaqian + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef _BOARD_H_ +#define _BOARD_H_ + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/* + * Setup for a Generic AT32F415 board. + */ + +/* + * Board identifier. + */ +#define BOARD_GENERIC_AT32_F415XX +#define BOARD_NAME "GENERIC AT32F415 board" + +/* + * Board oscillators-related settings. + */ +#if !defined(AT32_LEXTCLK) +#define AT32_LEXTCLK 32768 +#endif + +#if !defined(AT32_HEXTCLK) +#define AT32_HEXTCLK 8000000 +#endif + +/* + * MCU type, supported types are defined in ./os/hal/platforms/hal_lld.h. + */ +#define AT32F415KB + +/* + * IO pins assignments. + */ +#define GPIOA_PIN0 0U +#define GPIOA_PIN1 1U +#define GPIOA_PIN2 2U +#define GPIOA_PIN3 3U +#define GPIOA_PIN4 4U +#define GPIOA_PIN5 5U +#define GPIOA_PIN6 6U +#define GPIOA_PIN7 7U +#define GPIOA_PIN8 8U +#define GPIOA_PIN9 9U +#define GPIOA_PIN10 10U +#define GPIOA_PIN11 11U +#define GPIOA_PIN12 12U +#define GPIOA_SWDIO 13U +#define GPIOA_SWCLK 14U +#define GPIOA_PIN15 15U + +#define GPIOB_PIN0 0U +#define GPIOB_PIN1 1U +#define GPIOB_PIN2 2U +#define GPIOB_PIN3 3U +#define GPIOB_PIN4 4U +#define GPIOB_PIN5 5U +#define GPIOB_PIN6 6U +#define GPIOB_PIN7 7U +#define GPIOB_PIN8 8U +#define GPIOB_PIN9 9U +#define GPIOB_PIN10 10U +#define GPIOB_PIN11 11U +#define GPIOB_PIN12 12U +#define GPIOB_PIN13 13U +#define GPIOB_PIN14 14U +#define GPIOB_PIN15 15U + +#define GPIOC_PIN0 0U +#define GPIOC_PIN1 1U +#define GPIOC_PIN2 2U +#define GPIOC_PIN3 3U +#define GPIOC_PIN4 4U +#define GPIOC_PIN5 5U +#define GPIOC_PIN6 6U +#define GPIOC_PIN7 7U +#define GPIOC_PIN8 8U +#define GPIOC_PIN9 9U +#define GPIOC_PIN10 10U +#define GPIOC_PIN11 11U +#define GPIOC_PIN12 12U +#define GPIOC_PIN13 13U +#define GPIOC_PIN14 14U +#define GPIOC_PIN15 15U + +#define GPIOD_HEXT_IN 0U +#define GPIOD_HEXT_OUT 1U +#define GPIOD_PIN2 2U + +#define GPIOF_PIN4 4U +#define GPIOF_PIN5 5U +#define GPIOF_PIN6 6U +#define GPIOF_PIN7 7U + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/* + * I/O ports initial setup, this configuration is established soon after reset + * in the initialization code. + * + * The digits have the following meaning: + * 0 - Analog input. + * 1 - Push Pull output 10MHz. + * 2 - Push Pull output 2MHz. + * 3 - Push Pull output 50MHz. + * 4 - Digital input. + * 5 - Open Drain output 10MHz. + * 6 - Open Drain output 2MHz. + * 7 - Open Drain output 50MHz. + * 8 - Digital input with Pull-Up or Pull-Down resistor depending on ODT. + * 9 - Alternate Push Pull output 10MHz. + * A - Alternate Push Pull output 2MHz. + * B - Alternate Push Pull output 50MHz. + * C - Reserved. + * D - Alternate Open Drain output 10MHz. + * E - Alternate Open Drain output 2MHz. + * F - Alternate Open Drain output 50MHz. + * Please refer to the AT32 Reference Manual for details. + */ + +/* + * Port A setup. + */ +#define VAL_GPIOACFGLR 0x88888B88 /* PA7...PA0 */ +#define VAL_GPIOACFGHR 0x888888B8 /* PA15...PA8 */ +#define VAL_GPIOAODT 0xFFFFFFFF + +/* + * Port B setup. + */ +#define VAL_GPIOBCFGLR 0x88888888 /* PB7...PB0 */ +#define VAL_GPIOBCFGHR 0x88888888 /* PB15...PB8 */ +#define VAL_GPIOBODT 0xFFFFFFFF + +/* + * Port C setup. + */ +#define VAL_GPIOCCFGLR 0x88888888 /* PC7...PC0 */ +#define VAL_GPIOCCFGHR 0x88888888 /* PC15...PC8 */ +#define VAL_GPIOCODT 0xFFFFFFFF + +/* + * Port D setup. + * Everything input with pull-up except: + * PD0 - Normal input (GPIOD_HEXT_IN). + * PD1 - Normal input (GPIOD_HEXT_OUT). + */ +#define VAL_GPIODCFGLR 0x88888844 /* PD7...PD0 */ +#define VAL_GPIODCFGHR 0x88888888 /* PD15...PD8 */ +#define VAL_GPIODODT 0xFFFFFFFF + +/* + * Port F setup. + */ +#define VAL_GPIOFCFGLR 0x88888888 /* PF7...PF0 */ +#define VAL_GPIOFCFGHR 0x88888888 /* PF15...PF8 */ +#define VAL_GPIOFODT 0xFFFFFFFF + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#if !defined(_FROM_ASM_) +#ifdef __cplusplus +extern "C" { +#endif + void boardInit(void); +#ifdef __cplusplus +} +#endif +#endif /* _FROM_ASM_ */ + +#endif /* _BOARD_H_ */ diff --git a/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.mk b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.mk new file mode 100644 index 00000000000..842e3359059 --- /dev/null +++ b/platforms/chibios/boards/GENERIC_AT32_F415XX/board/board.mk @@ -0,0 +1,9 @@ +# List of all the board related files. +BOARDSRC = $(BOARD_PATH)/board/board.c + +# Required include directories +BOARDINC = $(BOARD_PATH)/board + +# Shared variables +ALLCSRC += $(BOARDSRC) +ALLINC += $(BOARDINC) diff --git a/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/config.h b/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/config.h new file mode 100644 index 00000000000..da60447a0af --- /dev/null +++ b/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/config.h @@ -0,0 +1,13 @@ +// Copyright 2023-2024 HorrorTroll +// Copyright 2023-2024 Zhaqian +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#define BOARD_OTG_VBUSIG + +#define USB_ENDPOINTS_ARE_REORDERABLE + +#ifndef EARLY_INIT_PERFORM_BOOTLOADER_JUMP +# define EARLY_INIT_PERFORM_BOOTLOADER_JUMP TRUE +#endif diff --git a/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/mcuconf.h b/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/mcuconf.h new file mode 100644 index 00000000000..d148379fe1d --- /dev/null +++ b/platforms/chibios/boards/GENERIC_AT32_F415XX/configs/mcuconf.h @@ -0,0 +1,236 @@ +/* + ChibiOS - Copyright (C) 2006..2020 Giovanni Di Sirio + ChibiOS - Copyright (C) 2023..2024 HorrorTroll + ChibiOS - Copyright (C) 2023..2024 Zhaqian + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef MCUCONF_H +#define MCUCONF_H + +/* + * AT32F415 drivers configuration. + * The following settings override the default settings present in + * the various device driver implementation headers. + * Note that the settings for each driver only have effect if the whole + * driver is enabled in halconf.h. + * + * IRQ priorities: + * 15...0 Lowest...Highest. + * + * DMA priorities: + * 0...3 Lowest...Highest. + */ + +#define AT32F415_MCUCONF + +/* + * General settings. + */ +#define AT32_NO_INIT FALSE + +/* + * HAL driver system settings. + */ +#define AT32_HICK_ENABLED TRUE +#define AT32_LICK_ENABLED FALSE +#define AT32_HEXT_ENABLED TRUE +#define AT32_LEXT_ENABLED FALSE +#define AT32_SCLKSEL AT32_SCLKSEL_PLL +#define AT32_PLLRCS AT32_PLLRCS_HEXT +#define AT32_PLLHEXTDIV AT32_PLLHEXTDIV_DIV1 +#define AT32_PLLCFGEN AT32_PLLCFGEN_SOLID +#define AT32_PLLMULT_VALUE 18 +#define AT32_PLL_FR_VALUE 4 +#define AT32_PLL_MS_VALUE 1 +#define AT32_PLL_NS_VALUE 72 +#define AT32_AHBDIV AT32_AHBDIV_DIV1 +#define AT32_APB1DIV AT32_APB1DIV_DIV2 +#define AT32_APB2DIV AT32_APB2DIV_DIV2 +#define AT32_ADCDIV AT32_ADCDIV_DIV4 +#define AT32_USB_CLOCK_REQUIRED TRUE +#define AT32_USBDIV AT32_USBDIV_DIV3 +#define AT32_CLKOUT_SEL AT32_CLKOUT_SEL_NOCLOCK +#define AT32_CLKOUTDIV AT32_CLKOUTDIV_DIV1 +#define AT32_ERTCSEL AT32_ERTCSEL_HEXTDIV +#define AT32_PVM_ENABLE FALSE +#define AT32_PVMSEL AT32_PVMSEL_LEV1 + +/* + * IRQ system settings. + */ +#define AT32_IRQ_EXINT0_PRIORITY 6 +#define AT32_IRQ_EXINT1_PRIORITY 6 +#define AT32_IRQ_EXINT2_PRIORITY 6 +#define AT32_IRQ_EXINT3_PRIORITY 6 +#define AT32_IRQ_EXINT4_PRIORITY 6 +#define AT32_IRQ_EXINT5_9_PRIORITY 6 +#define AT32_IRQ_EXINT10_15_PRIORITY 6 +#define AT32_IRQ_EXINT16_PRIORITY 6 +#define AT32_IRQ_EXINT17_PRIORITY 15 +#define AT32_IRQ_EXINT18_PRIORITY 6 +#define AT32_IRQ_EXINT19_PRIORITY 6 +#define AT32_IRQ_EXINT20_PRIORITY 6 +#define AT32_IRQ_EXINT21_PRIORITY 15 +#define AT32_IRQ_EXINT22_PRIORITY 15 + +#define AT32_IRQ_TMR1_BRK_TMR9_PRIORITY 7 +#define AT32_IRQ_TMR1_OVF_TMR10_PRIORITY 7 +#define AT32_IRQ_TMR1_HALL_TMR11_PRIORITY 7 +#define AT32_IRQ_TMR1_CH_PRIORITY 7 +#define AT32_IRQ_TMR2_PRIORITY 7 +#define AT32_IRQ_TMR3_PRIORITY 7 +#define AT32_IRQ_TMR4_PRIORITY 7 +#define AT32_IRQ_TMR5_PRIORITY 7 + +#define AT32_IRQ_USART1_PRIORITY 12 +#define AT32_IRQ_USART2_PRIORITY 12 +#define AT32_IRQ_USART3_PRIORITY 12 +#define AT32_IRQ_UART4_PRIORITY 12 +#define AT32_IRQ_UART5_PRIORITY 12 + +/* + * ADC driver system settings. + */ +#define AT32_ADC_USE_ADC1 FALSE +#define AT32_ADC_ADC1_DMA_PRIORITY 2 +#define AT32_ADC_ADC1_IRQ_PRIORITY 6 + +/* + * CAN driver system settings. + */ +#define AT32_CAN_USE_CAN1 FALSE +#define AT32_CAN_CAN1_IRQ_PRIORITY 11 + +/* + * DMA driver system settings. + */ +#define AT32_DMA_USE_DMAMUX TRUE + +/* + * GPT driver system settings. + */ +#define AT32_GPT_USE_TMR1 FALSE +#define AT32_GPT_USE_TMR2 FALSE +#define AT32_GPT_USE_TMR3 FALSE +#define AT32_GPT_USE_TMR4 FALSE +#define AT32_GPT_USE_TMR5 FALSE +#define AT32_GPT_USE_TMR9 FALSE +#define AT32_GPT_USE_TMR10 FALSE +#define AT32_GPT_USE_TMR11 FALSE + +/* + * I2C driver system settings. + */ +#define AT32_I2C_USE_I2C1 FALSE +#define AT32_I2C_USE_I2C2 FALSE +#define AT32_I2C_BUSY_TIMEOUT 50 +#define AT32_I2C_I2C1_IRQ_PRIORITY 5 +#define AT32_I2C_I2C2_IRQ_PRIORITY 5 +#define AT32_I2C_I2C1_DMA_PRIORITY 3 +#define AT32_I2C_I2C2_DMA_PRIORITY 3 +#define AT32_I2C_DMA_ERROR_HOOK(i2cp) osalSysHalt("DMA failure") + +/* + * ICU driver system settings. + */ +#define AT32_ICU_USE_TMR1 FALSE +#define AT32_ICU_USE_TMR2 FALSE +#define AT32_ICU_USE_TMR3 FALSE +#define AT32_ICU_USE_TMR4 FALSE +#define AT32_ICU_USE_TMR5 FALSE +#define AT32_ICU_USE_TMR9 FALSE +#define AT32_ICU_USE_TMR10 FALSE +#define AT32_ICU_USE_TMR11 FALSE + +/* + * PWM driver system settings. + */ +#define AT32_PWM_USE_TMR1 FALSE +#define AT32_PWM_USE_TMR2 FALSE +#define AT32_PWM_USE_TMR3 FALSE +#define AT32_PWM_USE_TMR4 FALSE +#define AT32_PWM_USE_TMR5 FALSE +#define AT32_PWM_USE_TMR9 FALSE +#define AT32_PWM_USE_TMR10 FALSE +#define AT32_PWM_USE_TMR11 FALSE + +/* + * RTC driver system settings. + */ +#define AT32_ERTC_DIVA_VALUE 32 +#define AT32_ERTC_DIVB_VALUE 1024 +#define AT32_ERTC_CTRL_INIT 0 +#define AT32_ERTC_TAMP_INIT 0 + +/* + * SDC driver system settings. + */ +#define AT32_SDC_SDIO_DMA_PRIORITY 3 +#define AT32_SDC_SDIO_IRQ_PRIORITY 9 +#define AT32_SDC_WRITE_TIMEOUT_MS 1000 +#define AT32_SDC_READ_TIMEOUT_MS 1000 +#define AT32_SDC_CLOCK_ACTIVATION_DELAY 10 +#define AT32_SDC_SDIO_UNALIGNED_SUPPORT TRUE + +/* + * SERIAL driver system settings. + */ +#define AT32_SERIAL_USE_USART1 FALSE +#define AT32_SERIAL_USE_USART2 FALSE +#define AT32_SERIAL_USE_USART3 FALSE +#define AT32_SERIAL_USE_UART4 FALSE +#define AT32_SERIAL_USE_UART5 FALSE + +/* + * SPI driver system settings. + */ +#define AT32_SPI_USE_SPI1 FALSE +#define AT32_SPI_USE_SPI2 FALSE +#define AT32_SPI_SPI1_DMA_PRIORITY 1 +#define AT32_SPI_SPI2_DMA_PRIORITY 1 +#define AT32_SPI_SPI1_IRQ_PRIORITY 10 +#define AT32_SPI_SPI2_IRQ_PRIORITY 10 +#define AT32_SPI_DMA_ERROR_HOOK(spip) osalSysHalt("DMA failure") + +/* + * ST driver system settings. + */ +#define AT32_ST_IRQ_PRIORITY 8 +#define AT32_ST_USE_TIMER 2 + +/* + * UART driver system settings. + */ +#define AT32_UART_USE_USART1 FALSE +#define AT32_UART_USE_USART2 FALSE +#define AT32_UART_USE_USART3 FALSE +#define AT32_UART_USART1_DMA_PRIORITY 0 +#define AT32_UART_USART2_DMA_PRIORITY 0 +#define AT32_UART_USART3_DMA_PRIORITY 0 +#define AT32_UART_DMA_ERROR_HOOK(uartp) osalSysHalt("DMA failure") + +/* + * USB driver system settings. + */ +#define AT32_USB_USE_OTG1 TRUE +#define AT32_USB_OTG1_IRQ_PRIORITY 14 +#define AT32_USB_OTG1_RX_FIFO_SIZE 512 + +/* + * WDG driver system settings. + */ +#define AT32_WDG_USE_WDT FALSE + +#endif /* MCUCONF_H */ diff --git a/platforms/chibios/bootloader.mk b/platforms/chibios/bootloader.mk index 5b6edd73ad3..e292e1e0b08 100644 --- a/platforms/chibios/bootloader.mk +++ b/platforms/chibios/bootloader.mk @@ -26,6 +26,7 @@ # stm32-dfu STM32 USB DFU in ROM # apm32-dfu APM32 USB DFU in ROM # wb32-dfu WB32 USB DFU in ROM +# at32-dfu AT32 USB DFU in ROM # tinyuf2 TinyUF2 # rp2040 Raspberry Pi RP2040 # Current options for RISC-V: @@ -119,6 +120,14 @@ ifeq ($(strip $(BOOTLOADER)), wb32-dfu) OPT_DEFS += -DBOOTLOADER_WB32_DFU BOOTLOADER_TYPE = wb32_dfu endif +ifeq ($(strip $(BOOTLOADER)), at32-dfu) + OPT_DEFS += -DBOOTLOADER_AT32_DFU + BOOTLOADER_TYPE = at32_dfu + + # Options to pass to dfu-util when flashing + DFU_ARGS ?= -d 2E3C:DF11 -a 0 -s 0x08000000:leave + DFU_SUFFIX_ARGS ?= -v 2E3C -p DF11 +endif ifeq ($(strip $(BOOTLOADER_TYPE)),) ifneq ($(strip $(BOOTLOADER)),) diff --git a/platforms/chibios/bootloaders/at32_dfu.c b/platforms/chibios/bootloaders/at32_dfu.c new file mode 100644 index 00000000000..f3a453d0fce --- /dev/null +++ b/platforms/chibios/bootloaders/at32_dfu.c @@ -0,0 +1,83 @@ +// Copyright 2021-2023 QMK +// Copyright 2023-2024 HorrorTroll +// Copyright 2023-2024 Zhaqian +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "bootloader.h" +#include "util.h" + +#include +#include +#include "wait.h" + +#ifndef AT32_BOOTLOADER_RAM_SYMBOL +# define AT32_BOOTLOADER_RAM_SYMBOL __ram0_end__ +#endif + +extern uint32_t AT32_BOOTLOADER_RAM_SYMBOL; + +/* This code should be checked whether it runs correctly on platforms */ +#define SYMVAL(sym) (uint32_t)(((uint8_t *)&(sym)) - ((uint8_t *)0)) +#define BOOTLOADER_MAGIC 0xDEADBEEF +#define MAGIC_ADDR (unsigned long *)(SYMVAL(AT32_BOOTLOADER_RAM_SYMBOL) - 4) + +__attribute__((weak)) void bootloader_marker_enable(void) { + uint32_t *marker = (uint32_t *)MAGIC_ADDR; + *marker = BOOTLOADER_MAGIC; // set magic flag => reset handler will jump into boot loader +} + +__attribute__((weak)) bool bootloader_marker_active(void) { + const uint32_t *marker = (const uint32_t *)MAGIC_ADDR; + return (*marker == BOOTLOADER_MAGIC) ? true : false; +} + +__attribute__((weak)) void bootloader_marker_disable(void) { + uint32_t *marker = (uint32_t *)MAGIC_ADDR; + *marker = 0; +} + +__attribute__((weak)) void bootloader_jump(void) { + bootloader_marker_enable(); + NVIC_SystemReset(); +} + +__attribute__((weak)) void mcu_reset(void) { + NVIC_SystemReset(); +} + +void enter_bootloader_mode_if_requested(void) { + if (bootloader_marker_active()) { + bootloader_marker_disable(); + + struct system_memory_vector_t { + uint32_t stack_top; + void (*entrypoint)(void); + }; + const struct system_memory_vector_t *bootloader = (const struct system_memory_vector_t *)(AT32_BOOTLOADER_ADDRESS); + + __disable_irq(); + +#if defined(__MPU_PRESENT) && (__MPU_PRESENT == 1U) + ARM_MPU_Disable(); +#endif + + SysTick->CTRL = 0; + SysTick->VAL = 0; + SysTick->LOAD = 0; + + // Clear interrupt enable and interrupt pending registers + for (int i = 0; i < ARRAY_SIZE(NVIC->ICER); i++) { + NVIC->ICER[i] = 0xFFFFFFFF; + NVIC->ICPR[i] = 0xFFFFFFFF; + } + + __set_CONTROL(0); + __set_MSP(bootloader->stack_top); + __enable_irq(); + + // Jump to bootloader + bootloader->entrypoint(); + while (true) { + } + } +} diff --git a/platforms/chibios/chibios_config.h b/platforms/chibios/chibios_config.h index 8f46fe07360..95136baf303 100644 --- a/platforms/chibios/chibios_config.h +++ b/platforms/chibios/chibios_config.h @@ -142,6 +142,19 @@ # endif #endif +// AT32 compatibility +#if defined(MCU_AT32) +# define CPU_CLOCK AT32_SYSCLK + +# if defined(AT32F415) +# define USE_GPIOV1 +# define USE_I2CV1 +# define PAL_MODE_ALTERNATE_OPENDRAIN PAL_MODE_AT32_ALTERNATE_OPENDRAIN +# define PAL_MODE_ALTERNATE_PUSHPULL PAL_MODE_AT32_ALTERNATE_PUSHPULL +# define AUDIO_PWM_PAL_MODE PAL_MODE_ALTERNATE_PUSHPULL +# endif +#endif + #if defined(GD32VF103) /* This chip has the same API as STM32F103, but uses different names for literally the same thing. * As of 4.7.2021 QMK is tailored to use STM32 defines/names, for compatibility sake diff --git a/platforms/chibios/drivers/analog.c b/platforms/chibios/drivers/analog.c index fb146df9367..7e1f87e6c95 100644 --- a/platforms/chibios/drivers/analog.c +++ b/platforms/chibios/drivers/analog.c @@ -22,7 +22,7 @@ # error "You need to set HAL_USE_ADC to TRUE in your halconf.h to use the ADC." #endif -#if !RP_ADC_USE_ADC1 && !STM32_ADC_USE_ADC1 && !STM32_ADC_USE_ADC2 && !STM32_ADC_USE_ADC3 && !STM32_ADC_USE_ADC4 && !WB32_ADC_USE_ADC1 +#if !RP_ADC_USE_ADC1 && !STM32_ADC_USE_ADC1 && !STM32_ADC_USE_ADC2 && !STM32_ADC_USE_ADC3 && !STM32_ADC_USE_ADC4 && !WB32_ADC_USE_ADC1 && !AT32_ADC_USE_ADC1 # error "You need to set one of the 'xxx_ADC_USE_ADCx' settings to TRUE in your mcuconf.h to use the ADC." #endif @@ -45,7 +45,7 @@ // Otherwise assume V3 #if defined(STM32F0XX) || defined(STM32L0XX) # define USE_ADCV1 -#elif defined(STM32F1XX) || defined(STM32F2XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) +#elif defined(STM32F1XX) || defined(STM32F2XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415) # define USE_ADCV2 #endif @@ -82,7 +82,7 @@ /* User configurable ADC options */ #ifndef ADC_COUNT -# if defined(RP2040) || defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) +# if defined(RP2040) || defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415) # define ADC_COUNT 1 # elif defined(STM32F3XX) || defined(STM32G4XX) # define ADC_COUNT 4 @@ -142,11 +142,16 @@ static ADCConversionGroup adcConversionGroup = { .cfgr1 = ADC_CFGR1_CONT | ADC_RESOLUTION, .smpr = ADC_SAMPLING_RATE, #elif defined(USE_ADCV2) -# if !defined(STM32F1XX) && !defined(GD32VF103) && !defined(WB32F3G71xx) && !defined(WB32FQ95xx) - .cr2 = ADC_CR2_SWSTART, // F103 seem very unhappy with, F401 seems very unhappy without... +# if !defined(STM32F1XX) && !defined(GD32VF103) && !defined(WB32F3G71xx) && !defined(WB32FQ95xx) && !defined(AT32F415) + .cr2 = ADC_CR2_SWSTART, // F103 seem very unhappy with, F401 seems very unhappy without... # endif +# if defined(AT32F415) + .spt2 = ADC_SPT2_CSPT_AN0(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN1(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN2(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN3(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN4(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN5(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN6(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN7(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN8(ADC_SAMPLING_RATE) | ADC_SPT2_CSPT_AN9(ADC_SAMPLING_RATE), + .spt1 = ADC_SPT1_CSPT_AN10(ADC_SAMPLING_RATE) | ADC_SPT1_CSPT_AN11(ADC_SAMPLING_RATE) | ADC_SPT1_CSPT_AN12(ADC_SAMPLING_RATE) | ADC_SPT1_CSPT_AN13(ADC_SAMPLING_RATE) | ADC_SPT1_CSPT_AN14(ADC_SAMPLING_RATE) | ADC_SPT1_CSPT_AN15(ADC_SAMPLING_RATE), +# else .smpr2 = ADC_SMPR2_SMP_AN0(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN1(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN2(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN3(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN4(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN5(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN6(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN7(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN8(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN9(ADC_SAMPLING_RATE), .smpr1 = ADC_SMPR1_SMP_AN10(ADC_SAMPLING_RATE) | ADC_SMPR1_SMP_AN11(ADC_SAMPLING_RATE) | ADC_SMPR1_SMP_AN12(ADC_SAMPLING_RATE) | ADC_SMPR1_SMP_AN13(ADC_SAMPLING_RATE) | ADC_SMPR1_SMP_AN14(ADC_SAMPLING_RATE) | ADC_SMPR1_SMP_AN15(ADC_SAMPLING_RATE), +# endif #elif defined(RP2040) // RP2040 does not have any extra config here #else @@ -242,7 +247,7 @@ __attribute__((weak)) adc_mux pinToMux(pin_t pin) { case F9: return TO_MUX( ADC_CHANNEL_IN7, 2 ); case F10: return TO_MUX( ADC_CHANNEL_IN8, 2 ); # endif -#elif defined(STM32F1XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) +#elif defined(STM32F1XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415) case A0: return TO_MUX( ADC_CHANNEL_IN0, 0 ); case A1: return TO_MUX( ADC_CHANNEL_IN1, 0 ); case A2: return TO_MUX( ADC_CHANNEL_IN2, 0 ); @@ -344,7 +349,7 @@ __attribute__((weak)) adc_mux pinToMux(pin_t pin) { static inline ADCDriver* intToADCDriver(uint8_t adcInt) { switch (adcInt) { -#if RP_ADC_USE_ADC1 || STM32_ADC_USE_ADC1 || WB32_ADC_USE_ADC1 +#if RP_ADC_USE_ADC1 || STM32_ADC_USE_ADC1 || WB32_ADC_USE_ADC1 || AT32_ADC_USE_ADC1 case 0: return &ADCD1; #endif @@ -391,7 +396,11 @@ int16_t adc_read(adc_mux mux) { // TODO: fix previous assumption of only 1 input... adcConversionGroup.chselr = 1 << mux.input; /*no macro to convert N to ADC_CHSELR_CHSEL1*/ #elif defined(USE_ADCV2) +# if defined(AT32F415) + adcConversionGroup.osq3 = ADC_OSQ3_OSN1_N(mux.input); +# else adcConversionGroup.sqr3 = ADC_SQR3_SQ1_N(mux.input); +# endif #elif defined(RP2040) adcConversionGroup.channel_mask = 1 << mux.input; #else diff --git a/platforms/chibios/drivers/serial_usart.c b/platforms/chibios/drivers/serial_usart.c index 767ef8726fc..becf3afbce1 100644 --- a/platforms/chibios/drivers/serial_usart.c +++ b/platforms/chibios/drivers/serial_usart.c @@ -9,6 +9,17 @@ #if defined(SERIAL_USART_CONFIG) static QMKSerialConfig serial_config = SERIAL_USART_CONFIG; +#elif defined(MCU_AT32) /* AT32 MCUs */ +static QMKSerialConfig serial_config = { + .speed = (SERIAL_USART_SPEED), + .ctrl1 = (SERIAL_USART_CTRL1), + .ctrl2 = (SERIAL_USART_CTRL2), +# if !defined(SERIAL_USART_FULL_DUPLEX) + .ctrl3 = ((SERIAL_USART_CTRL3) | USART_CTRL3_SLBEN) /* activate half-duplex mode */ +# else + .ctrl3 = (SERIAL_USART_CTRL3) +# endif +}; #elif defined(MCU_STM32) /* STM32 MCUs */ static QMKSerialConfig serial_config = { # if HAL_USE_SERIAL @@ -160,7 +171,7 @@ inline bool serial_transport_receive_blocking(uint8_t* destination, const size_t * @brief Initiate pins for USART peripheral. Half-duplex configuration. */ __attribute__((weak)) void usart_init(void) { -# if defined(MCU_STM32) /* STM32 MCUs */ +# if defined(MCU_STM32) || defined(MCU_AT32) /* STM32 and AT32 MCUs */ # if defined(USE_GPIOV1) palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE_OPENDRAIN); # else @@ -183,7 +194,7 @@ __attribute__((weak)) void usart_init(void) { * @brief Initiate pins for USART peripheral. Full-duplex configuration. */ __attribute__((weak)) void usart_init(void) { -# if defined(MCU_STM32) /* STM32 MCUs */ +# if defined(MCU_STM32) || defined(MCU_AT32) /* STM32 and AT32 MCUs */ # if defined(USE_GPIOV1) palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE_PUSHPULL); palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_INPUT); diff --git a/platforms/chibios/drivers/serial_usart.h b/platforms/chibios/drivers/serial_usart.h index dec8a292e98..dbd7673273f 100644 --- a/platforms/chibios/drivers/serial_usart.h +++ b/platforms/chibios/drivers/serial_usart.h @@ -74,40 +74,75 @@ typedef SIOConfig QMKSerialConfig; # endif #endif -#if !defined(USART_CR1_M0) -# define USART_CR1_M0 USART_CR1_M // some platforms (f1xx) dont have this so -#endif +#if defined(MCU_STM32) /* STM32 MCUs */ +# if !defined(USART_CR1_M0) +# define USART_CR1_M0 USART_CR1_M // some platforms (f1xx) dont have this so +# endif -#if !defined(SERIAL_USART_CR1) -# define SERIAL_USART_CR1 (USART_CR1_PCE | USART_CR1_PS | USART_CR1_M0) // parity enable, odd parity, 9 bit length -#endif +# if !defined(SERIAL_USART_CR1) +# define SERIAL_USART_CR1 (USART_CR1_PCE | USART_CR1_PS | USART_CR1_M0) // parity enable, odd parity, 9 bit length +# endif -#if !defined(SERIAL_USART_CR2) -# define SERIAL_USART_CR2 (USART_CR2_STOP_1) // 2 stop bits -#endif +# if !defined(SERIAL_USART_CR2) +# define SERIAL_USART_CR2 (USART_CR2_STOP_1) // 2 stop bits +# endif -#if !defined(SERIAL_USART_CR3) -# define SERIAL_USART_CR3 0 -#endif +# if !defined(SERIAL_USART_CR3) +# define SERIAL_USART_CR3 0 +# endif -#if defined(USART1_REMAP) -# define USART_REMAP \ - do { \ - (AFIO->MAPR |= AFIO_MAPR_USART1_REMAP); \ - } while (0) -#elif defined(USART2_REMAP) -# define USART_REMAP \ - do { \ - (AFIO->MAPR |= AFIO_MAPR_USART2_REMAP); \ - } while (0) -#elif defined(USART3_PARTIALREMAP) -# define USART_REMAP \ - do { \ - (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_PARTIALREMAP); \ - } while (0) -#elif defined(USART3_FULLREMAP) -# define USART_REMAP \ - do { \ - (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_FULLREMAP); \ - } while (0) +# if defined(USART1_REMAP) +# define USART_REMAP \ + do { \ + (AFIO->MAPR |= AFIO_MAPR_USART1_REMAP); \ + } while (0) +# elif defined(USART2_REMAP) +# define USART_REMAP \ + do { \ + (AFIO->MAPR |= AFIO_MAPR_USART2_REMAP); \ + } while (0) +# elif defined(USART3_PARTIALREMAP) +# define USART_REMAP \ + do { \ + (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_PARTIALREMAP); \ + } while (0) +# elif defined(USART3_FULLREMAP) +# define USART_REMAP \ + do { \ + (AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_FULLREMAP); \ + } while (0) +# endif +#elif defined(MCU_AT32) /* AT32 MCUs */ +# if !defined(USART_CTRL1_DBN0) +# define USART_CTRL1_DBN0 USART_CTRL1_DBN +# endif + +# if !defined(SERIAL_USART_CTRL1) +# define SERIAL_USART_CTRL1 (USART_CTRL1_PEN | USART_CTRL1_PSEL | USART_CTRL1_DBN0) // parity enable, odd parity, 9 bit length +# endif + +# if !defined(SERIAL_USART_CTRL2) +# define SERIAL_USART_CTRL2 (USART_CTRL2_STOPBN_1) // 2 stop bits +# endif + +# if !defined(SERIAL_USART_CTRL3) +# define SERIAL_USART_CTRL3 0 +# endif + +# if defined(USART1_REMAP) +# define USART_REMAP \ + do { \ + (IOMUX->REMAP |= IOMUX_REMAP_USART1_MUX); \ + } while (0) +# elif defined(USART3_PARTIALREMAP) +# define USART_REMAP \ + do { \ + (IOMUX->REMAP |= IOMUX_REMAP_USART3_MUX_MUX1); \ + } while (0) +# elif defined(USART3_FULLREMAP) +# define USART_REMAP \ + do { \ + (IOMUX->REMAP |= IOMUX_REMAP_USART3_MUX_MUX2); \ + } while (0) +# endif #endif diff --git a/platforms/chibios/drivers/spi_master.c b/platforms/chibios/drivers/spi_master.c index cbe765e233d..6417b7077f5 100644 --- a/platforms/chibios/drivers/spi_master.c +++ b/platforms/chibios/drivers/spi_master.c @@ -103,9 +103,15 @@ bool spi_start_extended(spi_start_config_t *start_config) { roundedDivisor <<= 1; } +# if defined(AT32F415) + if (roundedDivisor < 2 || roundedDivisor > 1024) { + return false; + } +# else if (roundedDivisor < 2 || roundedDivisor > 256) { return false; } +# endif #endif #if defined(K20x) || defined(KL2x) @@ -240,6 +246,59 @@ bool spi_start_extended(spi_start_config_t *start_config) { spiConfig.SSPCR0 |= SPI_SSPCR0_SPH; // Clock phase: sample on second edge transition break; } +#elif defined(AT32F415) + spiConfig.ctrl1 = 0; + + if (lsbFirst) { + spiConfig.ctrl1 |= SPI_CTRL1_LTF; + } + + switch (mode) { + case 0: + break; + case 1: + spiConfig.ctrl1 |= SPI_CTRL1_CLKPHA; + break; + case 2: + spiConfig.ctrl1 |= SPI_CTRL1_CLKPOL; + break; + case 3: + spiConfig.ctrl1 |= SPI_CTRL1_CLKPHA | SPI_CTRL1_CLKPOL; + break; + } + + switch (roundedDivisor) { + case 2: + break; + case 4: + spiConfig.ctrl1 |= SPI_CTRL1_MDIV_0; + break; + case 8: + spiConfig.ctrl1 |= SPI_CTRL1_MDIV_1; + break; + case 16: + spiConfig.ctrl1 |= SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0; + break; + case 32: + spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2; + break; + case 64: + spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_0; + break; + case 128: + spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1; + break; + case 256: + spiConfig.ctrl1 |= SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0; + break; + case 512: + spiConfig.ctrl2 |= SPI_CTRL1_MDIV_3; + break; + case 1024: + spiConfig.ctrl2 |= SPI_CTRL1_MDIV_3; + spiConfig.ctrl1 |= SPI_CTRL1_MDIV_0; + break; + } #else spiConfig.cr1 = 0; diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c index f49c4a45b0b..fed16d20b1a 100644 --- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c +++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl.c @@ -33,7 +33,7 @@ static inline uint32_t detect_flash_size(void) { #elif defined(FLASH_SIZE) return FLASH_SIZE; #elif defined(FLASHSIZE_BASE) -# if defined(QMK_MCU_SERIES_STM32F0XX) || defined(QMK_MCU_SERIES_STM32F1XX) || defined(QMK_MCU_SERIES_STM32F3XX) || defined(QMK_MCU_SERIES_STM32F4XX) || defined(QMK_MCU_SERIES_STM32G4XX) || defined(QMK_MCU_SERIES_STM32L0XX) || defined(QMK_MCU_SERIES_STM32L4XX) || defined(QMK_MCU_SERIES_GD32VF103) +# if defined(QMK_MCU_SERIES_STM32F0XX) || defined(QMK_MCU_SERIES_STM32F1XX) || defined(QMK_MCU_SERIES_STM32F3XX) || defined(QMK_MCU_SERIES_STM32F4XX) || defined(QMK_MCU_SERIES_STM32G4XX) || defined(QMK_MCU_SERIES_STM32L0XX) || defined(QMK_MCU_SERIES_STM32L4XX) || defined(QMK_MCU_SERIES_AT32F415) || defined(QMK_MCU_SERIES_GD32VF103) return ((*(uint32_t *)FLASHSIZE_BASE) & 0xFFFFU) << 10U; // this register has the flash size in kB, so we convert it to bytes # elif defined(QMK_MCU_SERIES_STM32L1XX) # error This MCU family has an uncommon flash size register definition and has not been implemented. Perhaps try using the true EEPROM on the MCU instead? diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h index 0f0fa694e9c..f09f824bd8b 100644 --- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h +++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h @@ -16,6 +16,8 @@ # define BACKING_STORE_WRITE_SIZE 4 // from hal_efl_lld.c # elif defined(QMK_MCU_FAMILY_WB32) # define BACKING_STORE_WRITE_SIZE 8 // from hal_efl_lld.c +# elif defined(QMK_MCU_FAMILY_AT32) +# define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c # elif defined(QMK_MCU_FAMILY_STM32) # if defined(STM32_FLASH_LINE_SIZE) // from some family's stm32_registry.h file # define BACKING_STORE_WRITE_SIZE (STM32_FLASH_LINE_SIZE) diff --git a/platforms/chibios/drivers/ws2812_bitbang.c b/platforms/chibios/drivers/ws2812_bitbang.c index fce1963d0ae..a88c5ff619d 100644 --- a/platforms/chibios/drivers/ws2812_bitbang.c +++ b/platforms/chibios/drivers/ws2812_bitbang.c @@ -11,7 +11,7 @@ /* Adapted from https://github.com/bigjosh/SimpleNeoPixelDemo/ */ #ifndef WS2812_BITBANG_NOP_FUDGE -# if defined(STM32F0XX) || defined(STM32F1XX) || defined(GD32VF103) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(WB32F3G71xx) || defined(WB32FQ95xx) +# if defined(STM32F0XX) || defined(STM32F1XX) || defined(GD32VF103) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415) # define WS2812_BITBANG_NOP_FUDGE 0.4 # else # if defined(RP2040) diff --git a/platforms/chibios/drivers/ws2812_pwm.c b/platforms/chibios/drivers/ws2812_pwm.c index c46e9171ab8..50927b849a4 100644 --- a/platforms/chibios/drivers/ws2812_pwm.c +++ b/platforms/chibios/drivers/ws2812_pwm.c @@ -40,6 +40,9 @@ #if (STM32_DMA_SUPPORTS_DMAMUX == TRUE) && !defined(WS2812_PWM_DMAMUX_ID) # error "please consult your MCU's datasheet and specify in your config.h: #define WS2812_PWM_DMAMUX_ID STM32_DMAMUX1_TIM?_UP" #endif +#if (AT32_DMA_SUPPORTS_DMAMUX == TRUE) && !defined(WS2812_PWM_DMAMUX_CHANNEL) && !defined(WS2812_PWM_DMAMUX_ID) +# error "please consult your MCU's datasheet and specify in your config.h: #define WS2812_PWM_DMAMUX_CHANNEL 1, #define WS2812_PWM_DMAMUX_ID AT32_DMAMUX_TMR?_OVERFLOW" +#endif /* Summarize https://www.st.com/resource/en/application_note/an4013-stm32-crossseries-timer-overview-stmicroelectronics.pdf to * figure out if we are using a 32bit timer. This is needed to setup the DMA controller correctly. @@ -269,6 +272,14 @@ typedef uint32_t ws2812_buffer_t; # define WS2812_PWM_DMA_PERIPHERAL_WIDTH STM32_DMA_CR_PSIZE_HWORD typedef uint16_t ws2812_buffer_t; # endif +#elif defined(AT32F415) +# define WS2812_PWM_DMA_MEMORY_WIDTH AT32_DMA_CCTRL_MWIDTH_BYTE +# if defined(WS2812_PWM_TIMER_32BIT) +# define WS2812_PWM_DMA_PERIPHERAL_WIDTH AT32_DMA_CCTRL_PWIDTH_WORD +# else +# define WS2812_PWM_DMA_PERIPHERAL_WIDTH AT32_DMA_CCTRL_PWIDTH_HWORD +# endif +typedef uint8_t ws2812_buffer_t; #else # define WS2812_PWM_DMA_MEMORY_WIDTH STM32_DMA_CR_MSIZE_BYTE # if defined(WS2812_PWM_TIMER_32BIT) @@ -309,8 +320,13 @@ void ws2812_init(void) { [0 ... 3] = {.mode = PWM_OUTPUT_DISABLED, .callback = NULL}, // Channels default to disabled [WS2812_PWM_CHANNEL - 1] = {.mode = WS2812_PWM_OUTPUT_MODE, .callback = NULL}, // Turn on the channel we care about }, +#if defined(AT32F415) + .ctrl2 = 0, + .iden = AT32_TMR_IDEN_OVFDEN, // DMA on update event for next period +#else .cr2 = 0, .dier = TIM_DIER_UDE, // DMA on update event for next period +#endif }; //#pragma GCC diagnostic pop // Restore command-line warning options @@ -321,6 +337,11 @@ void ws2812_init(void) { dmaStreamSetSource(WS2812_PWM_DMA_STREAM, ws2812_frame_buffer); dmaStreamSetDestination(WS2812_PWM_DMA_STREAM, &(WS2812_PWM_DRIVER.tim->CCR[WS2812_PWM_CHANNEL - 1])); // Ziel ist der An-Zeit im Cap-Comp-Register dmaStreamSetMode(WS2812_PWM_DMA_STREAM, WB32_DMA_CHCFG_HWHIF(WS2812_PWM_DMA_CHANNEL) | WB32_DMA_CHCFG_DIR_M2P | WB32_DMA_CHCFG_PSIZE_WORD | WB32_DMA_CHCFG_MSIZE_WORD | WB32_DMA_CHCFG_MINC | WB32_DMA_CHCFG_CIRC | WB32_DMA_CHCFG_TCIE | WB32_DMA_CHCFG_PL(3)); +#elif defined(AT32F415) + dmaStreamAlloc(WS2812_PWM_DMA_STREAM - AT32_DMA_STREAM(0), 10, NULL, NULL); + dmaStreamSetPeripheral(WS2812_PWM_DMA_STREAM, &(WS2812_PWM_DRIVER.tmr->CDT[WS2812_PWM_CHANNEL - 1])); // Ziel ist der An-Zeit im Cap-Comp-Register + dmaStreamSetMemory0(WS2812_PWM_DMA_STREAM, ws2812_frame_buffer); + dmaStreamSetMode(WS2812_PWM_DMA_STREAM, AT32_DMA_CCTRL_DTD_M2P | WS2812_PWM_DMA_PERIPHERAL_WIDTH | WS2812_PWM_DMA_MEMORY_WIDTH | AT32_DMA_CCTRL_MINCM | AT32_DMA_CCTRL_LM | AT32_DMA_CCTRL_CHPL(3)); #else dmaStreamAlloc(WS2812_PWM_DMA_STREAM - STM32_DMA_STREAM(0), 10, NULL, NULL); dmaStreamSetPeripheral(WS2812_PWM_DMA_STREAM, &(WS2812_PWM_DRIVER.tim->CCR[WS2812_PWM_CHANNEL - 1])); // Ziel ist der An-Zeit im Cap-Comp-Register @@ -335,6 +356,11 @@ void ws2812_init(void) { dmaSetRequestSource(WS2812_PWM_DMA_STREAM, WS2812_PWM_DMAMUX_ID); #endif +#if (AT32_DMA_SUPPORTS_DMAMUX == TRUE) + // If the MCU has a DMAMUX we need to assign the correct resource + dmaSetRequestSource(WS2812_PWM_DMA_STREAM, WS2812_PWM_DMAMUX_CHANNEL, WS2812_PWM_DMAMUX_ID); +#endif + // Start DMA dmaStreamEnable(WS2812_PWM_DMA_STREAM); diff --git a/platforms/chibios/drivers/ws2812_spi.c b/platforms/chibios/drivers/ws2812_spi.c index a1357edec57..d1792b871bf 100644 --- a/platforms/chibios/drivers/ws2812_spi.c +++ b/platforms/chibios/drivers/ws2812_spi.c @@ -40,26 +40,53 @@ // Define SPI config speed // baudrate should target 3.2MHz +#if defined(AT32F415) +# if WS2812_SPI_DIVISOR == 2 +# define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (0) +# elif WS2812_SPI_DIVISOR == 4 +# define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_0) +# elif WS2812_SPI_DIVISOR == 8 +# define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_1) +# elif WS2812_SPI_DIVISOR == 16 // default +# define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0) +# elif WS2812_SPI_DIVISOR == 32 +# define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2) +# elif WS2812_SPI_DIVISOR == 64 +# define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_0) +# elif WS2812_SPI_DIVISOR == 128 +# define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1) +# elif WS2812_SPI_DIVISOR == 256 +# define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0) +# elif WS2812_SPI_DIVISOR == 512 +# define WS2812_SPI_DIVISOR_CTRL2_MDIV_X (SPI_CTRL1_MDIV_3) +# elif WS2812_SPI_DIVISOR == 1024 +# define WS2812_SPI_DIVISOR_CTRL2_MDIV_X (SPI_CTRL1_MDIV_3) +# define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_0) +# else +# error "Configured WS2812_SPI_DIVISOR value is not supported at this time." +# endif +#else // F072 fpclk = 48MHz // 48/16 = 3Mhz -#if WS2812_SPI_DIVISOR == 2 -# define WS2812_SPI_DIVISOR_CR1_BR_X (0) -#elif WS2812_SPI_DIVISOR == 4 -# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_0) -#elif WS2812_SPI_DIVISOR == 8 -# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1) -#elif WS2812_SPI_DIVISOR == 16 // default -# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1 | SPI_CR1_BR_0) -#elif WS2812_SPI_DIVISOR == 32 -# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2) -#elif WS2812_SPI_DIVISOR == 64 -# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_0) -#elif WS2812_SPI_DIVISOR == 128 -# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1) -#elif WS2812_SPI_DIVISOR == 256 -# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0) -#else -# error "Configured WS2812_SPI_DIVISOR value is not supported at this time." +# if WS2812_SPI_DIVISOR == 2 +# define WS2812_SPI_DIVISOR_CR1_BR_X (0) +# elif WS2812_SPI_DIVISOR == 4 +# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_0) +# elif WS2812_SPI_DIVISOR == 8 +# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1) +# elif WS2812_SPI_DIVISOR == 16 // default +# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1 | SPI_CR1_BR_0) +# elif WS2812_SPI_DIVISOR == 32 +# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2) +# elif WS2812_SPI_DIVISOR == 64 +# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_0) +# elif WS2812_SPI_DIVISOR == 128 +# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1) +# elif WS2812_SPI_DIVISOR == 256 +# define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0) +# else +# error "Configured WS2812_SPI_DIVISOR value is not supported at this time." +# endif #endif // Use SPI circular buffer @@ -176,8 +203,16 @@ void ws2812_init(void) { NULL, // error_cb PAL_PORT(WS2812_DI_PIN), PAL_PAD(WS2812_DI_PIN), +# if defined(AT32F415) + WS2812_SPI_DIVISOR_CTRL1_MDIV_X, +# if (WS2812_SPI_DIVISOR == 512 || WS2812_SPI_DIVISOR == 1024) + WS2812_SPI_DIVISOR_CTRL2_MDIV_X, +# endif + 0 +# else WS2812_SPI_DIVISOR_CR1_BR_X, 0 +# endif #endif }; diff --git a/platforms/chibios/flash.mk b/platforms/chibios/flash.mk index 525f177f9eb..f4db17a58bf 100644 --- a/platforms/chibios/flash.mk +++ b/platforms/chibios/flash.mk @@ -113,6 +113,8 @@ else ifeq ($(strip $(MCU_FAMILY)),STM32) $(UNSYNC_OUTPUT_CMD) && $(call EXEC_DFU_UTIL) else ifeq ($(strip $(MCU_FAMILY)),WB32) $(UNSYNC_OUTPUT_CMD) && $(call EXEC_WB32_DFU_UPDATER) +else ifeq ($(strip $(MCU_FAMILY)),AT32) + $(UNSYNC_OUTPUT_CMD) && $(call EXEC_DFU_UTIL) else ifeq ($(strip $(MCU_FAMILY)),GD32V) $(UNSYNC_OUTPUT_CMD) && $(call EXEC_DFU_UTIL) else diff --git a/platforms/chibios/mcu_selection.mk b/platforms/chibios/mcu_selection.mk index a1597fa1e91..086a2b31c60 100644 --- a/platforms/chibios/mcu_selection.mk +++ b/platforms/chibios/mcu_selection.mk @@ -809,6 +809,40 @@ ifneq ($(findstring WB32FQ95, $(MCU)),) WB32_BOOTLOADER_ADDRESS ?= 0x1FFFE000 endif +ifneq ($(findstring AT32F415, $(MCU)),) + # Cortex version + MCU = cortex-m4 + + # ARM version, CORTEX-M0/M1 are 6, CORTEX-M3/M4/M7 are 7 + ARMV = 7 + + ## chip/board settings + # - the next two should match the directories in + # /os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES) + # OR + # /os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) + MCU_FAMILY = AT32 + MCU_SERIES = AT32F415 + + # Linker script to use + # - it should exist either in /os/common/startup/ARMCMx/compilers/GCC/ld/ + # or /ld/ + MCU_LDSCRIPT ?= AT32F415xB + + # Startup code to use + # - it should exist in /os/common/startup/ARMCMx/compilers/GCC/mk/ + MCU_STARTUP ?= at32f415 + + # Board: it should exist either in /os/hal/boards/, + # /boards/, or drivers/boards/ + BOARD ?= GENERIC_AT32_F415XX + + USE_FPU ?= no + + # Bootloader address for AT32 DFU + AT32_BOOTLOADER_ADDRESS ?= 0x1FFFAC00 +endif + ifneq ($(findstring GD32VF103, $(MCU)),) # RISC-V MCU = risc-v diff --git a/platforms/chibios/platform.mk b/platforms/chibios/platform.mk index 169707966ff..cf9fac251e4 100644 --- a/platforms/chibios/platform.mk +++ b/platforms/chibios/platform.mk @@ -155,6 +155,10 @@ ifdef WB32_BOOTLOADER_ADDRESS OPT_DEFS += -DWB32_BOOTLOADER_ADDRESS=$(WB32_BOOTLOADER_ADDRESS) endif +ifdef AT32_BOOTLOADER_ADDRESS + OPT_DEFS += -DAT32_BOOTLOADER_ADDRESS=$(AT32_BOOTLOADER_ADDRESS) +endif + # Work out if we need to set up the include for the bootloader definitions ifneq ("$(wildcard $(KEYBOARD_PATH_5)/bootloader_defs.h)","") OPT_DEFS += -include $(KEYBOARD_PATH_5)/bootloader_defs.h From 36b5559b997cedd14a352aa70891558936b8b3a3 Mon Sep 17 00:00:00 2001 From: Drashna Jaelre Date: Wed, 20 Nov 2024 22:31:54 -0800 Subject: [PATCH 2/2] [Core] Add Layer Lock feature (#23430) Co-authored-by: Daniel <1767914+iamdanielv@users.noreply.github.com> Co-authored-by: Pascal Getreuer Co-authored-by: Pascal Getreuer <50221757+getreuer@users.noreply.github.com> --- builddefs/generic_features.mk | 1 + .../keycodes/keycodes_0.0.6_quantum.hjson | 7 + data/mappings/info_config.hjson | 3 + data/schemas/keyboard.jsonschema | 6 + docs/_sidebar.json | 1 + docs/feature_layers.md | 3 + docs/features/layer_lock.md | 139 +++++++++ docs/keycodes.md | 8 + quantum/keyboard.c | 7 + quantum/keycodes.h | 6 +- quantum/layer_lock.c | 81 +++++ quantum/layer_lock.h | 135 +++++++++ quantum/process_keycode/process_layer_lock.c | 95 ++++++ quantum/process_keycode/process_layer_lock.h | 69 +++++ quantum/quantum.c | 7 + quantum/quantum.h | 4 + tests/layer_lock/config.h | 8 + tests/layer_lock/test.mk | 8 + tests/layer_lock/test_layer_lock.cpp | 284 ++++++++++++++++++ tests/test_common/keycode_table.cpp | 1 + 20 files changed, 871 insertions(+), 2 deletions(-) create mode 100644 docs/features/layer_lock.md create mode 100644 quantum/layer_lock.c create mode 100644 quantum/layer_lock.h create mode 100644 quantum/process_keycode/process_layer_lock.c create mode 100644 quantum/process_keycode/process_layer_lock.h create mode 100644 tests/layer_lock/config.h create mode 100644 tests/layer_lock/test.mk create mode 100644 tests/layer_lock/test_layer_lock.cpp diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk index dc34a642307..f14f4408770 100644 --- a/builddefs/generic_features.mk +++ b/builddefs/generic_features.mk @@ -36,6 +36,7 @@ GENERIC_FEATURES = \ HAPTIC \ KEY_LOCK \ KEY_OVERRIDE \ + LAYER_LOCK \ LEADER \ MAGIC \ MOUSEKEY \ diff --git a/data/constants/keycodes/keycodes_0.0.6_quantum.hjson b/data/constants/keycodes/keycodes_0.0.6_quantum.hjson index 814cd66a5b1..be3285f9e43 100644 --- a/data/constants/keycodes/keycodes_0.0.6_quantum.hjson +++ b/data/constants/keycodes/keycodes_0.0.6_quantum.hjson @@ -3,5 +3,12 @@ "0x7C20": "!delete!", // old QK_OUTPUT_AUTO "0x7C21": "!delete!", // old QK_OUTPUT_USB "0x7C22": "!delete!", // old QK_OUTPUT_BLUETOOTH + "0x7C7B": { + "group": "quantum", + "key": "QK_LAYER_LOCK", + "aliases": [ + "QK_LLCK" + ] + } } } diff --git a/data/mappings/info_config.hjson b/data/mappings/info_config.hjson index 4c895cf5d50..8d3e3c7f0ee 100644 --- a/data/mappings/info_config.hjson +++ b/data/mappings/info_config.hjson @@ -64,6 +64,9 @@ "WEAR_LEVELING_BACKING_SIZE": {"info_key": "eeprom.wear_leveling.backing_size", "value_type": "int", "to_json": false}, "WEAR_LEVELING_LOGICAL_SIZE": {"info_key": "eeprom.wear_leveling.logical_size", "value_type": "int", "to_json": false}, + // Layer locking + "LAYER_LOCK_IDLE_TIMEOUT": {"info_key": "layer_lock.timeout", "value_type": "int"}, + // Indicators "LED_CAPS_LOCK_PIN": {"info_key": "indicators.caps_lock"}, "LED_NUM_LOCK_PIN": {"info_key": "indicators.num_lock"}, diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index 9f1f6dd74a5..ec87680fa05 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema @@ -375,6 +375,12 @@ } }, "keycodes": {"$ref": "qmk.definitions.v1#/keycode_decl_array"}, + "layer_lock": { + "type": "object", + "properties": { + "timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"} + } + }, "layout_aliases": { "type": "object", "additionalProperties": {"$ref": "qmk.definitions.v1#/layout_macro"} diff --git a/docs/_sidebar.json b/docs/_sidebar.json index 7f353ad351d..15c3cbec317 100644 --- a/docs/_sidebar.json +++ b/docs/_sidebar.json @@ -123,6 +123,7 @@ { "text": "Key Lock", "link": "/features/key_lock" }, { "text": "Key Overrides", "link": "/features/key_overrides" }, { "text": "Layers", "link": "/feature_layers" }, + { "text": "Layer Lock", "link": "/features/layer_lock" }, { "text": "One Shot Keys", "link": "/one_shot_keys" }, { "text": "OS Detection", "link": "/features/os_detection" }, { "text": "Raw HID", "link": "/features/rawhid" }, diff --git a/docs/feature_layers.md b/docs/feature_layers.md index 30ab7132226..45f02fe536d 100644 --- a/docs/feature_layers.md +++ b/docs/feature_layers.md @@ -17,6 +17,9 @@ These functions allow you to activate layers in various ways. Note that layers a * `TO(layer)` - activates *layer* and de-activates all other layers (except your default layer). This function is special, because instead of just adding/removing one layer to your active layer stack, it will completely replace your current active layers, uniquely allowing you to replace higher layers with a lower one. This is activated on keydown (as soon as the key is pressed). * `TT(layer)` - Layer Tap-Toggle. If you hold the key down, *layer* is activated, and then is de-activated when you let go (like `MO`). If you repeatedly tap it, the layer will be toggled on or off (like `TG`). It needs 5 taps by default, but you can change this by defining `TAPPING_TOGGLE` -- for example, `#define TAPPING_TOGGLE 2` to toggle on just two taps. +See also the [Layer Lock key](features/layer_lock), which locks the highest +active layer until pressed again. + ### Caveats {#caveats} Currently, the `layer` argument of `LT()` is limited to layers 0-15, and the `kc` argument to the [Basic Keycode set](keycodes_basic), meaning you can't use keycodes like `LCTL()`, `KC_TILD`, or anything greater than `0xFF`. This is because QMK uses 16-bit keycodes, of which 4 bits are used for the function identifier and 4 bits for the layer, leaving only 8 bits for the keycode. diff --git a/docs/features/layer_lock.md b/docs/features/layer_lock.md new file mode 100644 index 00000000000..aaf323acccd --- /dev/null +++ b/docs/features/layer_lock.md @@ -0,0 +1,139 @@ +# Layer Lock + +Some [layer switches](../feature_layers#switching-and-toggling-layers) access +the layer by holding the key, including momentary layer `MO(layer)` and layer +tap `LT(layer, key)` keys. You may sometimes need to stay on the layer for a +long period of time. Layer Lock "locks" the current layer to stay on, supposing +it was accessed by one of: + + * `MO(layer)` momentary layer switch + * `LT(layer, key)` layer tap + * `OSL(layer)` one-shot layer + * `TT(layer)` layer tap toggle + * `LM(layer, mod)` layer-mod key (the layer is locked, but not the mods) + +Press the Layer Lock key again to unlock the layer. Additionally, when a layer +is locked, layer switch keys that turn off the layer such as `TO(other_layer)` +will unlock it. + + +## How do I enable Layer Lock + +In your rules.mk, add: + +```make +LAYER_LOCK_ENABLE = yes +``` + +Pick a key in your keymap on a layer you intend to lock, and assign it the +keycode `QK_LAYER_LOCK` (short alias `QK_LLCK`). Note that locking the base +layer has no effect, so typically, this key is used on layers above the base +layer. + + +## Example use + +Consider a keymap with the following base layer. + +![Base layer with a MO(NAV) key.](https://i.imgur.com/DkEhj9x.png) + +The highlighted key is a momentary layer switch `MO(NAV)`. Holding it accesses a +navigation layer. + +![Nav layer with a Layer Lock key.](https://i.imgur.com/2wUZNWk.png) + + +Holding the NAV key is fine for brief use, but awkward to continue holding when +using navigation functions continuously. The Layer Lock key comes to the rescue: + +1. Hold the NAV key, activating the navigation layer. +2. Tap Layer Lock. +3. Release NAV. The navigation layer stays on. +4. Make use of the arrow keys, etc. +5. Tap Layer Lock or NAV again to turn the navigation layer back off. + +A variation that would also work is to put the Layer Lock key on the base layer +and make other layers transparent (`KC_TRNS`) in that position. Pressing the +Layer Lock key locks (or unlocks) the highest active layer, regardless of which +layer the Layer Lock key is on. + + +## Idle timeout + +Optionally, Layer Lock may be configured to unlock if the keyboard is idle +for some time. In config.h, define `LAYER_LOCK_IDLE_TIMEOUT` in units of +milliseconds: + +```c +#define LAYER_LOCK_IDLE_TIMEOUT 60000 // Turn off after 60 seconds. +``` + + +## Functions + +Use the following functions to query and manipulate the layer lock state. + +| Function | Description | +|----------------------------|------------------------------------| +| `is_layer_locked(layer)` | Checks whether `layer` is locked. | +| `layer_lock_on(layer)` | Locks and turns on `layer`. | +| `layer_lock_off(layer)` | Unlocks and turns off `layer`. | +| `layer_lock_invert(layer)` | Toggles whether `layer` is locked. | + + +## Representing the current Layer Lock state + +There is an optional callback `layer_lock_set_user()` that gets called when a +layer is locked or unlocked. This is useful to represent the current lock state +for instance by setting an LED. In keymap.c, define + +```c +bool layer_lock_set_user(layer_state_t locked_layers) { + // Do something like `set_led(is_layer_locked(NAV));` + return true; +} +``` + +The argument `locked_layers` is a bitfield in which the kth bit is on if the kth +layer is locked. Alternatively, you can use `is_layer_locked(layer)` to check if +a given layer is locked. + + +## Combine Layer Lock with a mod-tap + +It is possible to create a [mod-tap MT key](../mod_tap) that acts as a modifier +on hold and Layer Lock on tap. Since Layer Lock is not a [basic +keycode](../keycodes_basic), attempting `MT(mod, QK_LLCK)` is invalid does not +work directly, yet this effect can be achieved through [changing the tap +function](../mod_tap#changing-tap-function). For example, the following +implements a `SFTLLCK` key that acts as Shift on hold and Layer Lock on tap: + +```c +#define SFTLLCK LSFT_T(KC_0) + +// Use SFTLLCK in your keymap... + +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case SFTLLCK: + if (record->tap.count) { + if (record->event.pressed) { + // Toggle the lock on the highest layer. + layer_lock_invert(get_highest_layer(layer_state)); + } + return false; + } + break; + + // Other macros... + } + return true; +} +``` + +In the above, `KC_0` is an arbitrary placeholder for the tapping keycode. This +keycode will never be sent, so any basic keycode will do. In +`process_record_user()`, the tap press event is changed to toggle the lock on +the highest layer. Layer Lock can be combined with a [layer-tap LT +key](../feature_layers#switching-and-toggling-layers) similarly. + diff --git a/docs/keycodes.md b/docs/keycodes.md index 3665747a0bf..cae6418266a 100644 --- a/docs/keycodes.md +++ b/docs/keycodes.md @@ -387,6 +387,14 @@ See also: [Key Lock](features/key_lock) |---------|--------------------------------------------------------------| |`QK_LOCK`|Hold down the next key pressed, until the key is pressed again| +## Layer Lock {#layer-lock} + +See also: [Layer Lock](features/layer_lock) + +|Key |Aliases |Description | +|---------------|---------|----------------------------------| +|`QK_LAYER_LOCK`|`QK_LLCK`|Locks or unlocks the highest layer| + ## Layer Switching {#layer-switching} See also: [Layer Switching](feature_layers#switching-and-toggling-layers) diff --git a/quantum/keyboard.c b/quantum/keyboard.c index df1dc1c3ee0..8db81a4b391 100644 --- a/quantum/keyboard.c +++ b/quantum/keyboard.c @@ -140,6 +140,9 @@ along with this program. If not, see . #ifdef OS_DETECTION_ENABLE # include "os_detection.h" #endif +#if defined(LAYER_LOCK_ENABLE) && LAYER_LOCK_IDLE_TIMEOUT > 0 +# include "layer_lock.h" +#endif // LAYER_LOCK_ENABLE static uint32_t last_input_modification_time = 0; uint32_t last_input_activity_time(void) { @@ -655,6 +658,10 @@ void quantum_task(void) { #ifdef SECURE_ENABLE secure_task(); #endif + +#if defined(LAYER_LOCK_ENABLE) && LAYER_LOCK_IDLE_TIMEOUT > 0 + layer_lock_task(); +#endif } /** \brief Main task that is repeatedly called as fast as possible. */ diff --git a/quantum/keycodes.h b/quantum/keycodes.h index 51b7eb6c9a4..e9da5105ce4 100644 --- a/quantum/keycodes.h +++ b/quantum/keycodes.h @@ -759,6 +759,7 @@ enum qk_keycode_defines { QK_TRI_LAYER_UPPER = 0x7C78, QK_REPEAT_KEY = 0x7C79, QK_ALT_REPEAT_KEY = 0x7C7A, + QK_LAYER_LOCK = 0x7C7B, QK_KB_0 = 0x7E00, QK_KB_1 = 0x7E01, QK_KB_2 = 0x7E02, @@ -1445,6 +1446,7 @@ enum qk_keycode_defines { TL_UPPR = QK_TRI_LAYER_UPPER, QK_REP = QK_REPEAT_KEY, QK_AREP = QK_ALT_REPEAT_KEY, + QK_LLCK = QK_LAYER_LOCK, }; // Range Helpers @@ -1501,7 +1503,7 @@ enum qk_keycode_defines { #define IS_UNDERGLOW_KEYCODE(code) ((code) >= QK_UNDERGLOW_TOGGLE && (code) <= QK_UNDERGLOW_SPEED_DOWN) #define IS_RGB_KEYCODE(code) ((code) >= RGB_MODE_PLAIN && (code) <= RGB_MODE_TWINKLE) #define IS_RGB_MATRIX_KEYCODE(code) ((code) >= QK_RGB_MATRIX_ON && (code) <= QK_RGB_MATRIX_SPEED_DOWN) -#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_ALT_REPEAT_KEY) +#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_LAYER_LOCK) #define IS_KB_KEYCODE(code) ((code) >= QK_KB_0 && (code) <= QK_KB_31) #define IS_USER_KEYCODE(code) ((code) >= QK_USER_0 && (code) <= QK_USER_31) @@ -1527,6 +1529,6 @@ enum qk_keycode_defines { #define UNDERGLOW_KEYCODE_RANGE QK_UNDERGLOW_TOGGLE ... QK_UNDERGLOW_SPEED_DOWN #define RGB_KEYCODE_RANGE RGB_MODE_PLAIN ... RGB_MODE_TWINKLE #define RGB_MATRIX_KEYCODE_RANGE QK_RGB_MATRIX_ON ... QK_RGB_MATRIX_SPEED_DOWN -#define QUANTUM_KEYCODE_RANGE QK_BOOTLOADER ... QK_ALT_REPEAT_KEY +#define QUANTUM_KEYCODE_RANGE QK_BOOTLOADER ... QK_LAYER_LOCK #define KB_KEYCODE_RANGE QK_KB_0 ... QK_KB_31 #define USER_KEYCODE_RANGE QK_USER_0 ... QK_USER_31 diff --git a/quantum/layer_lock.c b/quantum/layer_lock.c new file mode 100644 index 00000000000..9ee3c307dca --- /dev/null +++ b/quantum/layer_lock.c @@ -0,0 +1,81 @@ +// Copyright 2022-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "layer_lock.h" +#include "quantum_keycodes.h" + +#ifndef NO_ACTION_LAYER +// The current lock state. The kth bit is on if layer k is locked. +layer_state_t locked_layers = 0; + +// Layer Lock timer to disable layer lock after X seconds inactivity +# if defined(LAYER_LOCK_IDLE_TIMEOUT) && LAYER_LOCK_IDLE_TIMEOUT > 0 +uint32_t layer_lock_timer = 0; + +void layer_lock_task(void) { + if (locked_layers && timer_elapsed32(layer_lock_timer) > LAYER_LOCK_IDLE_TIMEOUT) { + layer_lock_all_off(); + layer_lock_timer = timer_read32(); + } +} +# endif // LAYER_LOCK_IDLE_TIMEOUT > 0 + +bool is_layer_locked(uint8_t layer) { + return locked_layers & ((layer_state_t)1 << layer); +} + +void layer_lock_invert(uint8_t layer) { + const layer_state_t mask = (layer_state_t)1 << layer; + if ((locked_layers & mask) == 0) { // Layer is being locked. +# ifndef NO_ACTION_ONESHOT + if (layer == get_oneshot_layer()) { + reset_oneshot_layer(); // Reset so that OSL doesn't turn layer off. + } +# endif // NO_ACTION_ONESHOT + layer_on(layer); +# if defined(LAYER_LOCK_IDLE_TIMEOUT) && LAYER_LOCK_IDLE_TIMEOUT > 0 + layer_lock_timer = timer_read32(); +# endif // LAYER_LOCK_IDLE_TIMEOUT > 0 + } else { // Layer is being unlocked. + layer_off(layer); + } + layer_lock_set_kb(locked_layers ^= mask); +} + +// Implement layer_lock_on/off by deferring to layer_lock_invert. +void layer_lock_on(uint8_t layer) { + if (!is_layer_locked(layer)) { + layer_lock_invert(layer); + } +} + +void layer_lock_off(uint8_t layer) { + if (is_layer_locked(layer)) { + layer_lock_invert(layer); + } +} + +void layer_lock_all_off(void) { + layer_and(~locked_layers); + locked_layers = 0; + layer_lock_set_kb(locked_layers); +} + +__attribute__((weak)) bool layer_lock_set_kb(layer_state_t locked_layers) { + return layer_lock_set_user(locked_layers); +} +__attribute__((weak)) bool layer_lock_set_user(layer_state_t locked_layers) { + return true; +} +#endif // NO_ACTION_LAYER diff --git a/quantum/layer_lock.h b/quantum/layer_lock.h new file mode 100644 index 00000000000..6d7285da2a6 --- /dev/null +++ b/quantum/layer_lock.h @@ -0,0 +1,135 @@ +// Copyright 2022-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @file layer_lock.h + * @brief Layer Lock, a key to stay in the current layer. + * + * Overview + * -------- + * + * Layers are often accessed by holding a button, e.g. with a momentary layer + * switch `MO(layer)` or layer tap `LT(layer, key)` key. But you may sometimes + * want to "lock" or "toggle" the layer so that it stays on without having to + * hold down a button. One way to do that is with a tap-toggle `TT` layer key, + * but here is an alternative. + * + * This library implements a "Layer Lock key". When tapped, it "locks" the + * highest layer to stay active, assuming the layer was activated by one of the + * following keys: + * + * * `MO(layer)` momentary layer switch + * * `LT(layer, key)` layer tap + * * `OSL(layer)` one-shot layer + * * `TT(layer)` layer tap toggle + * * `LM(layer, mod)` layer-mod key (the layer is locked, but not the mods) + * + * Tapping the Layer Lock key again unlocks and turns off the layer. + * + * @note When a layer is "locked", other layer keys such as `TO(layer)` or + * manually calling `layer_off(layer)` will override and unlock the layer. + * + * Configuration + * ------------- + * + * Optionally, a timeout may be defined so that Layer Lock disables + * automatically if not keys are pressed for `LAYER_LOCK_IDLE_TIMEOUT` + * milliseconds. Define `LAYER_LOCK_IDLE_TIMEOUT` in your config.h, for instance + * + * #define LAYER_LOCK_IDLE_TIMEOUT 60000 // Turn off after 60 seconds. + * + * and call `layer_lock_task()` from your `matrix_scan_user()` in keymap.c: + * + * void matrix_scan_user(void) { + * layer_lock_task(); + * // Other tasks... + * } + * + * For full documentation, see + * + */ + +#pragma once + +#include +#include +#include "action_layer.h" +#include "action_util.h" + +/** + * Handler function for Layer Lock. + * + * In your keymap, define a custom keycode to use for Layer Lock. Then handle + * Layer Lock from your `process_record_user` function by calling + * `process_layer_lock`, passing your custom keycode for the `lock_keycode` arg: + * + * #include "features/layer_lock.h" + * + * bool process_record_user(uint16_t keycode, keyrecord_t* record) { + * if (!process_layer_lock(keycode, record, LLOCK)) { return false; } + * // Your macros ... + * + * return true; + * } + */ + +#ifndef NO_ACTION_LAYER +/** Returns true if `layer` is currently locked. */ +bool is_layer_locked(uint8_t layer); + +/** Locks and turns on `layer`. */ +void layer_lock_on(uint8_t layer); + +/** Unlocks and turns off `layer`. */ +void layer_lock_off(uint8_t layer); + +/** Unlocks and turns off all locked layers. */ +void layer_lock_all_off(void); + +/** Toggles whether `layer` is locked. */ +void layer_lock_invert(uint8_t layer); + +/** + * Optional callback that gets called when a layer is locked or unlocked. + * + * This is useful to represent the current lock state, e.g. by setting an LED or + * playing a sound. In your keymap, define + * + * void layer_lock_set_user(layer_state_t locked_layers) { + * // Do something like `set_led(is_layer_locked(NAV));` + * } + * + * @param locked_layers Bitfield in which the kth bit represents whether the + * kth layer is on. + */ +bool layer_lock_set_kb(layer_state_t locked_layers); +bool layer_lock_set_user(layer_state_t locked_layers); + +void layer_lock_task(void); +#else // NO_ACTION_LAYER +static inline bool is_layer_locked(uint8_t layer) { + return false; +} +static inline void layer_lock_on(uint8_t layer) {} +static inline void layer_lock_off(uint8_t layer) {} +static inline void layer_lock_all_off(void) {} +static inline void layer_lock_invert(uint8_t layer) {} +static inline bool layer_lock_set_kb(layer_state_t locked_layers) { + return true; +} +static inline bool layer_lock_set_user(layer_state_t locked_layers) { + return true; +} +static inline void layer_lock_task(void) {} +#endif // NO_ACTION_LAYER diff --git a/quantum/process_keycode/process_layer_lock.c b/quantum/process_keycode/process_layer_lock.c new file mode 100644 index 00000000000..1e36d8844e8 --- /dev/null +++ b/quantum/process_keycode/process_layer_lock.c @@ -0,0 +1,95 @@ +// Copyright 2022-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @file layer_lock.c + * @brief Layer Lock implementation + * + * For full documentation, see + * + */ + +#include "layer_lock.h" +#include "process_layer_lock.h" +#include "quantum_keycodes.h" +#include "action_util.h" + +// The current lock state. The kth bit is on if layer k is locked. +extern layer_state_t locked_layers; +#if defined(LAYER_LOCK_IDLE_TIMEOUT) && LAYER_LOCK_IDLE_TIMEOUT > 0 +extern uint32_t layer_lock_timer; +#endif + +// Handles an event on an `MO` or `TT` layer switch key. +static bool handle_mo_or_tt(uint8_t layer, keyrecord_t* record) { + if (is_layer_locked(layer)) { + if (record->event.pressed) { // On press, unlock the layer. + layer_lock_invert(layer); + } + return false; // Skip default handling. + } + return true; +} + +bool process_layer_lock(uint16_t keycode, keyrecord_t* record) { +#ifndef NO_ACTION_LAYER +# if defined(LAYER_LOCK_IDLE_TIMEOUT) && LAYER_LOCK_IDLE_TIMEOUT > 0 + layer_lock_timer = timer_read32(); +# endif // LAYER_LOCK_IDLE_TIMEOUT > 0 + + // The intention is that locked layers remain on. If something outside of + // this feature turned any locked layers off, unlock them. + if ((locked_layers & ~layer_state) != 0) { + layer_lock_set_kb(locked_layers &= layer_state); + } + + if (keycode == QK_LAYER_LOCK) { + if (record->event.pressed) { // The layer lock key was pressed. + layer_lock_invert(get_highest_layer(layer_state)); + } + return false; + } + + switch (keycode) { + case QK_MOMENTARY ... QK_MOMENTARY_MAX: // `MO(layer)` keys. + return handle_mo_or_tt(QK_MOMENTARY_GET_LAYER(keycode), record); + + case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX: // `TT(layer)`. + return handle_mo_or_tt(QK_LAYER_TAP_TOGGLE_GET_LAYER(keycode), record); + + case QK_LAYER_MOD ... QK_LAYER_MOD_MAX: { // `LM(layer, mod)`. + uint8_t layer = QK_LAYER_MOD_GET_LAYER(keycode); + if (is_layer_locked(layer)) { + if (record->event.pressed) { // On press, unlock the layer. + layer_lock_invert(layer); + } else { // On release, clear the mods. + clear_mods(); + send_keyboard_report(); + } + return false; // Skip default handling. + } + } break; + +# ifndef NO_ACTION_TAPPING + case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: // `LT(layer, key)` keys. + if (record->tap.count == 0 && !record->event.pressed && is_layer_locked(QK_LAYER_TAP_GET_LAYER(keycode))) { + // Release event on a held layer-tap key where the layer is locked. + return false; // Skip default handling so that layer stays on. + } + break; +# endif // NO_ACTION_TAPPING + } +#endif // NO_ACTION_LAYER + return true; +} diff --git a/quantum/process_keycode/process_layer_lock.h b/quantum/process_keycode/process_layer_lock.h new file mode 100644 index 00000000000..b54c0f6f106 --- /dev/null +++ b/quantum/process_keycode/process_layer_lock.h @@ -0,0 +1,69 @@ +// Copyright 2022-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @file layer_lock.h + * @brief Layer Lock, a key to stay in the current layer. + * + * Overview + * -------- + * + * Layers are often accessed by holding a button, e.g. with a momentary layer + * switch `MO(layer)` or layer tap `LT(layer, key)` key. But you may sometimes + * want to "lock" or "toggle" the layer so that it stays on without having to + * hold down a button. One way to do that is with a tap-toggle `TT` layer key, + * but here is an alternative. + * + * This library implements a "Layer Lock key". When tapped, it "locks" the + * highest layer to stay active, assuming the layer was activated by one of the + * following keys: + * + * * `MO(layer)` momentary layer switch + * * `LT(layer, key)` layer tap + * * `OSL(layer)` one-shot layer + * * `TT(layer)` layer tap toggle + * * `LM(layer, mod)` layer-mod key (the layer is locked, but not the mods) + * + * Tapping the Layer Lock key again unlocks and turns off the layer. + * + * @note When a layer is "locked", other layer keys such as `TO(layer)` or + * manually calling `layer_off(layer)` will override and unlock the layer. + * + * Configuration + * ------------- + * + * Optionally, a timeout may be defined so that Layer Lock disables + * automatically if not keys are pressed for `LAYER_LOCK_IDLE_TIMEOUT` + * milliseconds. Define `LAYER_LOCK_IDLE_TIMEOUT` in your config.h, for instance + * + * #define LAYER_LOCK_IDLE_TIMEOUT 60000 // Turn off after 60 seconds. + * + * and call `layer_lock_task()` from your `matrix_scan_user()` in keymap.c: + * + * void matrix_scan_user(void) { + * layer_lock_task(); + * // Other tasks... + * } + * + * For full documentation, see + * + */ + +#pragma once + +#include +#include +#include "action.h" + +bool process_layer_lock(uint16_t keycode, keyrecord_t* record); diff --git a/quantum/quantum.c b/quantum/quantum.c index 874df1593a2..811ad2e7152 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -76,6 +76,10 @@ # include "process_unicode_common.h" #endif +#ifdef LAYER_LOCK_ENABLE +# include "process_layer_lock.h" +#endif // LAYER_LOCK_ENABLE + #ifdef AUDIO_ENABLE # ifndef GOODBYE_SONG # define GOODBYE_SONG SONG(GOODBYE_SOUND) @@ -400,6 +404,9 @@ bool process_record_quantum(keyrecord_t *record) { #ifdef TRI_LAYER_ENABLE process_tri_layer(keycode, record) && #endif +#ifdef LAYER_LOCK_ENABLE + process_layer_lock(keycode, record) && +#endif #ifdef BLUETOOTH_ENABLE process_connection(keycode, record) && #endif diff --git a/quantum/quantum.h b/quantum/quantum.h index b60d8a86bf7..71cf900f2de 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -240,6 +240,10 @@ extern layer_state_t layer_state; # include "os_detection.h" #endif +#ifdef LAYER_LOCK_ENABLE +# include "layer_lock.h" +#endif // LAYER_LOCK_ENABLE + void set_single_default_layer(uint8_t default_layer); void set_single_persistent_default_layer(uint8_t default_layer); diff --git a/tests/layer_lock/config.h b/tests/layer_lock/config.h new file mode 100644 index 00000000000..25d0b20c0ea --- /dev/null +++ b/tests/layer_lock/config.h @@ -0,0 +1,8 @@ +// Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "test_common.h" + +#define LAYER_LOCK_IDLE_TIMEOUT 1000 diff --git a/tests/layer_lock/test.mk b/tests/layer_lock/test.mk new file mode 100644 index 00000000000..05771e4dbf8 --- /dev/null +++ b/tests/layer_lock/test.mk @@ -0,0 +1,8 @@ +# Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) +# SPDX-License-Identifier: GPL-2.0-or-later + +# -------------------------------------------------------------------------------- +# Keep this file, even if it is empty, as a marker that this folder contains tests +# -------------------------------------------------------------------------------- + +LAYER_LOCK_ENABLE = yes diff --git a/tests/layer_lock/test_layer_lock.cpp b/tests/layer_lock/test_layer_lock.cpp new file mode 100644 index 00000000000..00742c3b436 --- /dev/null +++ b/tests/layer_lock/test_layer_lock.cpp @@ -0,0 +1,284 @@ +// Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "keycodes.h" +#include "test_common.hpp" + +using testing::_; + +class LayerLock : public TestFixture {}; + +TEST_F(LayerLock, LayerLockState) { + TestDriver driver; + KeymapKey key_a = KeymapKey(0, 0, 0, KC_A); + KeymapKey key_b = KeymapKey(1, 0, 0, KC_B); + KeymapKey key_c = KeymapKey(2, 0, 0, KC_C); + KeymapKey key_d = KeymapKey(3, 0, 0, KC_C); + + set_keymap({key_a, key_b, key_c, key_d}); + + EXPECT_FALSE(is_layer_locked(1)); + EXPECT_FALSE(is_layer_locked(2)); + EXPECT_FALSE(is_layer_locked(3)); + + layer_lock_invert(1); // Layer 1: unlocked -> locked + layer_lock_on(2); // Layer 2: unlocked -> locked + layer_lock_off(3); // Layer 3: stays unlocked + + // Layers 1 and 2 are now on. + EXPECT_TRUE(layer_state_is(1)); + EXPECT_TRUE(layer_state_is(2)); + // Layers 1 and 2 are now locked. + EXPECT_TRUE(is_layer_locked(1)); + EXPECT_TRUE(is_layer_locked(2)); + EXPECT_FALSE(is_layer_locked(3)); + + layer_lock_invert(1); // Layer 1: locked -> unlocked + layer_lock_on(2); // Layer 2: stays locked + layer_lock_on(3); // Layer 3: unlocked -> locked + + EXPECT_FALSE(layer_state_is(1)); + EXPECT_TRUE(layer_state_is(2)); + EXPECT_TRUE(layer_state_is(3)); + EXPECT_FALSE(is_layer_locked(1)); + EXPECT_TRUE(is_layer_locked(2)); + EXPECT_TRUE(is_layer_locked(3)); + + layer_lock_invert(1); // Layer 1: unlocked -> locked + layer_lock_off(2); // Layer 2: locked -> unlocked + + EXPECT_TRUE(layer_state_is(1)); + EXPECT_FALSE(layer_state_is(2)); + EXPECT_TRUE(layer_state_is(3)); + EXPECT_TRUE(is_layer_locked(1)); + EXPECT_FALSE(is_layer_locked(2)); + EXPECT_TRUE(is_layer_locked(3)); + + layer_lock_all_off(); // Layers 1 and 3: locked -> unlocked + + EXPECT_FALSE(layer_state_is(1)); + EXPECT_FALSE(layer_state_is(2)); + EXPECT_FALSE(layer_state_is(3)); + EXPECT_FALSE(is_layer_locked(1)); + EXPECT_FALSE(is_layer_locked(2)); + EXPECT_FALSE(is_layer_locked(3)); +} + +TEST_F(LayerLock, LayerLockMomentaryTest) { + TestDriver driver; + KeymapKey key_layer = KeymapKey(0, 0, 0, MO(1)); + KeymapKey key_a = KeymapKey(0, 1, 0, KC_A); + KeymapKey key_trns = KeymapKey(1, 0, 0, KC_TRNS); + KeymapKey key_ll = KeymapKey(1, 1, 0, QK_LAYER_LOCK); + + set_keymap({key_layer, key_a, key_trns, key_ll}); + + EXPECT_NO_REPORT(driver); + key_layer.press(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + EXPECT_FALSE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + tap_key(key_ll); + EXPECT_TRUE(layer_state_is(1)); + EXPECT_TRUE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + key_layer.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + EXPECT_TRUE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); + + // Pressing Layer Lock again unlocks the lock. + EXPECT_NO_REPORT(driver); + key_ll.press(); + run_one_scan_loop(); + EXPECT_FALSE(layer_state_is(1)); + EXPECT_FALSE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(LayerLock, LayerLockLayerTapTest) { + TestDriver driver; + KeymapKey key_layer = KeymapKey(0, 0, 0, LT(1, KC_B)); + KeymapKey key_a = KeymapKey(0, 1, 0, KC_A); + KeymapKey key_trns = KeymapKey(1, 0, 0, KC_TRNS); + KeymapKey key_ll = KeymapKey(1, 1, 0, QK_LAYER_LOCK); + + set_keymap({key_layer, key_a, key_trns, key_ll}); + + EXPECT_NO_REPORT(driver); + key_layer.press(); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + tap_key(key_ll); + EXPECT_TRUE(layer_state_is(1)); + EXPECT_TRUE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); + + // Pressing Layer Lock again unlocks the lock. + EXPECT_NO_REPORT(driver); + key_ll.press(); + run_one_scan_loop(); + EXPECT_FALSE(layer_state_is(1)); + EXPECT_FALSE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(LayerLock, LayerLockOneshotTapTest) { + TestDriver driver; + KeymapKey key_layer = KeymapKey(0, 0, 0, OSL(1)); + KeymapKey key_a = KeymapKey(0, 1, 0, KC_A); + KeymapKey key_trns = KeymapKey(1, 0, 0, KC_TRNS); + KeymapKey key_ll = KeymapKey(1, 1, 0, QK_LAYER_LOCK); + + set_keymap({key_layer, key_a, key_trns, key_ll}); + + EXPECT_NO_REPORT(driver); + tap_key(key_layer); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + tap_key(key_ll); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + EXPECT_TRUE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); + + // Pressing Layer Lock again unlocks the lock. + EXPECT_NO_REPORT(driver); + key_ll.press(); + run_one_scan_loop(); + EXPECT_FALSE(layer_state_is(1)); + EXPECT_FALSE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(LayerLock, LayerLockOneshotHoldTest) { + TestDriver driver; + KeymapKey key_layer = KeymapKey(0, 0, 0, OSL(1)); + KeymapKey key_a = KeymapKey(0, 1, 0, KC_A); + KeymapKey key_trns = KeymapKey(1, 0, 0, KC_TRNS); + KeymapKey key_ll = KeymapKey(1, 1, 0, QK_LAYER_LOCK); + + set_keymap({key_layer, key_a, key_trns, key_ll}); + + EXPECT_NO_REPORT(driver); + key_layer.press(); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + tap_key(key_ll); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + key_layer.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + EXPECT_TRUE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); + + // Pressing Layer Lock again unlocks the lock. + EXPECT_NO_REPORT(driver); + key_ll.press(); + run_one_scan_loop(); + EXPECT_FALSE(layer_state_is(1)); + EXPECT_FALSE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(LayerLock, LayerLockTimeoutTest) { + TestDriver driver; + KeymapKey key_layer = KeymapKey(0, 0, 0, MO(1)); + KeymapKey key_a = KeymapKey(0, 1, 0, KC_A); + KeymapKey key_trns = KeymapKey(1, 0, 0, KC_TRNS); + KeymapKey key_ll = KeymapKey(1, 1, 0, QK_LAYER_LOCK); + + set_keymap({key_layer, key_a, key_trns, key_ll}); + + EXPECT_NO_REPORT(driver); + key_layer.press(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + tap_key(key_ll); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + key_layer.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + EXPECT_TRUE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + idle_for(LAYER_LOCK_IDLE_TIMEOUT); + run_one_scan_loop(); + EXPECT_FALSE(layer_state_is(1)); + EXPECT_FALSE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(LayerLock, ToKeyOverridesLayerLock) { + TestDriver driver; + KeymapKey key_layer = KeymapKey(0, 0, 0, MO(1)); + KeymapKey key_to0 = KeymapKey(1, 0, 0, TO(0)); + KeymapKey key_ll = KeymapKey(1, 1, 0, QK_LAYER_LOCK); + + set_keymap({key_layer, key_to0, key_ll}); + + EXPECT_NO_REPORT(driver); + layer_lock_on(1); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + EXPECT_TRUE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + tap_key(key_to0); // TO(0) overrides Layer Lock and unlocks layer 1. + EXPECT_FALSE(layer_state_is(1)); + EXPECT_FALSE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(LayerLock, LayerClearOverridesLayerLock) { + TestDriver driver; + KeymapKey key_layer = KeymapKey(0, 0, 0, MO(1)); + KeymapKey key_a = KeymapKey(0, 1, 0, KC_A); + KeymapKey key_ll = KeymapKey(1, 1, 0, QK_LAYER_LOCK); + + set_keymap({key_layer, key_a, key_ll}); + + EXPECT_NO_REPORT(driver); + layer_lock_on(1); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + EXPECT_TRUE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_A)); + layer_clear(); // layer_clear() overrides Layer Lock and unlocks layer 1. + key_a.press(); + run_one_scan_loop(); + EXPECT_FALSE(layer_state_is(1)); + EXPECT_FALSE(is_layer_locked(1)); + VERIFY_AND_CLEAR(driver); +} diff --git a/tests/test_common/keycode_table.cpp b/tests/test_common/keycode_table.cpp index a520dd3f2bf..ae1a9edcd0f 100644 --- a/tests/test_common/keycode_table.cpp +++ b/tests/test_common/keycode_table.cpp @@ -699,6 +699,7 @@ std::map KEYCODE_ID_TABLE = { {QK_TRI_LAYER_UPPER, "QK_TRI_LAYER_UPPER"}, {QK_REPEAT_KEY, "QK_REPEAT_KEY"}, {QK_ALT_REPEAT_KEY, "QK_ALT_REPEAT_KEY"}, + {QK_LAYER_LOCK, "QK_LAYER_LOCK"}, {QK_KB_0, "QK_KB_0"}, {QK_KB_1, "QK_KB_1"}, {QK_KB_2, "QK_KB_2"},