From 5557bf3001c67b5b0e5e17444d543e56037369a9 Mon Sep 17 00:00:00 2001 From: Stefan Date: Sun, 6 Aug 2023 22:32:31 +0200 Subject: [PATCH] Add zephyr and ZMK packages, get gcc working. --- zephyr.scm | 112 ++++++++++ zephyr/apps.scm | 201 ++++++++++++++++++ zephyr/modules.scm | 223 ++++++++++++++++++++ zephyr/zmk.scm | 504 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1040 insertions(+) create mode 100644 zephyr.scm create mode 100644 zephyr/apps.scm create mode 100644 zephyr/modules.scm create mode 100644 zephyr/zmk.scm diff --git a/zephyr.scm b/zephyr.scm new file mode 100644 index 0000000..cd2d8de --- /dev/null +++ b/zephyr.scm @@ -0,0 +1,112 @@ +(define-module (zephyr) + #:use-module (embedded) + #:use-module (gnu packages bootloaders) + #:use-module (gnu packages python) + #:use-module (gnu packages python-xyz) + #:use-module (guix build-system copy) + #:use-module (guix build-system trivial) + #:use-module (guix gexp) + #:use-module (guix git-download) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix packages)) + +;; This is the common directory name for zephyr-modules to look for search-paths +;; to collect in the ZEPHYR_MODULES environment variable. +(define-public %zephyr-module "zephyr-module") + +;; This is the common directory prefix of zephyr-modules to collect for +;; search-paths in ZEPHYR_MODULES. The build-system of Zephyr searches for +;; a file zephyr/module.yml in all paths listed in the environment variable +;; ZEPHYR_MODULES. If that file is missing a name property, then the parent +;; directory name is used as the module name. Having two modules with the same +;; name is treated as an error. As Guix needs a common directory name for +;; search-path-specification, we need this intermediate directory prefix as a +;; pattern and to have unique module names. +(define-public %zephyr-module-tag "ZEPHYR-MODULE-TAG-") + +(define-public zephyr + (let ((version "3.3.0")) + (package + (name "zephyr") + (version version) + (source + (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/zephyrproject-rtos/zephyr") + (commit (string-append "zephyr-v" version)))) + (file-name (git-file-name name version)) + (sha256 (base32 + "05afpxlapqd7i9m03c8kj5ph9dbig94zhyb9mrwg41cm7725lw98")))) + (build-system copy-build-system) + (arguments + (list + #:install-plan + #~(list (list "." "zephyr-base")) + #:phases + #~(modify-phases %standard-phases + (add-after 'unpack 'set-USER_CACHE_DIR-and-BUILD_VERSION + (lambda _ + (substitute* "CMakeLists.txt" + (("if *\\(DEFINED BUILD_VERSION\\)") + (string-append + "if (NOT DEFINED BUILD_VERSION)\n" + " set(BUILD_VERSION " #$version ")\n" + "elseif (DEFINED BUILD_VERSION)"))) + (with-output-to-file + "cmake/modules/user_cache.cmake" + (lambda () + (display + "set(USER_CACHE_DIR ${CMAKE_BINARY_DIR}/cache)\n")))))))) + (native-search-paths + (list (search-path-specification + (variable "ZEPHYR_BASE") + (files '("zephyr-base")) + (separator #f)) + (search-path-specification + (variable "ZEPHYR_MODULES") + (files (list %zephyr-module)) + (separator ";") + (file-pattern (string-append "^" %zephyr-module-tag))))) + (home-page "https://zephyrproject.org") + (synopsis "Zephyr Project RTOS") + (description "The Zephyr Project is a scalable real-time operating system +(RTOS) supporting multiple hardware architectures, optimized for resource +constrained devices, and built with security in mind.") + (license license:apsl2)))) + +(define-public zephyr-3.2+zmk-fixes + (let ((revision "1") + (commit "0a586db7b58269fb08248b081bdc3b43452da5f4")) + (package/inherit zephyr + (name "zephyr+zmk-fixes") + (version (git-version "3.2.0" revision commit)) + (source + (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/zmkfirmware/zephyr") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 (base32 + "04mqgzhgjb43ybryvpwrbq2g9i0gk7wd7s2ds3fsrakl4hxqss78")))) + (home-page "https://github.com/zmkfirmware/zephyr")))) + +(define-public zephyr-build-tools + (package + (name "zephyr-build-tools") + (version "1.0.0") + (source #f) + (build-system trivial-build-system) + (arguments (list #:builder #~(mkdir #$output))) + (propagated-inputs (list dtc + python + python-pyelftools + python-pykwalify + python-pyyaml + python-packaging)) + (home-page "https://zephyrproject.org") + (synopsis "Zephyr build tools") + (description "Required build tools to build the Zephyr RTOS.") + (license license:apsl2))) + diff --git a/zephyr/apps.scm b/zephyr/apps.scm new file mode 100644 index 0000000..91edab8 --- /dev/null +++ b/zephyr/apps.scm @@ -0,0 +1,201 @@ +(define-module (zephyr apps) + #:use-module (embedded) + #:use-module (gnu packages) + #:use-module (gnu packages python-crypto) + #:use-module (gnu packages python-xyz) + #:use-module (gnu packages python-web) + #:use-module (gnu packages tls) + #:use-module (guix build-system cmake) + #:use-module (guix build-system python) + #:use-module (guix build-system trivial) + #:use-module (guix gexp) + #:use-module (guix git-download) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix packages) + #:use-module (guix utils) + #:use-module (ice-9 optargs) + #:use-module ((srfi srfi-1) #:select (delete-duplicates second)) + #:use-module (zephyr) + #:use-module (zephyr modules)) + +(define-public (complete-zephyr-application zephyr-application + zephyr + zephyr-build-tools + target + toolchain + c-toolchain + directory-to-install-from + source-prefix + target-prefix) + "Complete an incomplete ZEPHYR-APPLICATION package with target specific Zephyr +boilerplate." + (let ((extended-inputs (modify-inputs + (package-inputs zephyr-application) + (prepend zephyr zephyr-build-tools))) + (cross-compile-prefix (file-append toolchain "/bin/" target "-"))) + (package-with-c-toolchain + (package + (inherit zephyr-application) + (build-system cmake-build-system) + (arguments + (append + (substitute-keyword-arguments (package-arguments zephyr-application) + ((#:configure-flags configure-flags #~(list)) + #~(append #$configure-flags + (list + "-DZEPHYR_TOOLCHAIN_VARIANT=cross-compile" + (string-append "-DCROSS_COMPILE=" #$cross-compile-prefix))))) + (list + #:tests? #f + #:phases + #~(modify-phases %standard-phases + (replace 'install + (lambda _ + (let* ((source-prefix + #$(string-append directory-to-install-from "/" + source-prefix)) + (target-prefix + #$(string-append directory-to-install-from "/" + target-prefix)) + (source-prefix-length (string-length source-prefix)) + (primary-files-to-install + (list (string-append source-prefix ".uf2"))) + (files-to-install + (map (lambda (suffix) + (string-append source-prefix suffix)) + '(".bin" ".dts" ".elf" ".hex" ".map" ".stat"))) + (source-files + (find-files #$directory-to-install-from + (lambda (file stat) + (member file primary-files-to-install)) + #:directories? #f)) + (source-files + (if (null? source-files) + (find-files #$directory-to-install-from + (lambda (file stat) + (member file files-to-install)) + #:directories? #f) + source-files)) + (target-files + (map (lambda (file) + (string-replace + file target-prefix 0 source-prefix-length)) + source-files))) + (for-each rename-file source-files target-files) + (mkdir #$output) + (for-each (lambda (file) (install-file file #$output)) + target-files)))))))) + (inputs extended-inputs) + (license (delete-duplicates + (cons (package-license zephyr-application) + (map (compose package-license second) + (filter package? extended-inputs)))))) + c-toolchain))) + +(define*-public (make-zephyr-application-for-arm zephyr-application + #:key + (zephyr zephyr) + files-to-install + (directory-to-install-from + "zephyr") + (source-prefix "zephyr") + target-prefix) + "Make a Zephyr application for Arm microcontrollers by completing the +incomplete ZEPHYR-APPLICATION package with Arm specific Zephyr boilerplate." + (complete-zephyr-application zephyr-application + zephyr + zephyr-build-tools + "arm-none-eabi" + gcc12-cross-newlib-arm-none-eabi-toolchain + gcc12-cross-newlib-arm-none-eabi-c-toolchain + directory-to-install-from + source-prefix + (or target-prefix + (package-name zephyr-application)))) + +(define-public zephyr-hello-world + (make-zephyr-application-for-arm + (package + (name "zephyr-hello-world") + (version (package-version zephyr)) + (source (file-append zephyr "/zephyr-base/samples/hello_world")) + (build-system #f) + (arguments + (list + #:configure-flags + #~(list "-DBOARD=nucleo_wb55rg" + "-DCONFIG_BT=y" + "-DCONFIG_BT_STM32_IPM=y" + "-DCMAKE_C_FLAGS=-DCFG_BLE_LSE_SOURCE=1" + "-DCMAKE_BUILD_TYPE=RelMinSize"))) + (inputs (list zephyr-module-cmsis + zephyr-module-hal-stm32 + zephyr-module-tinycrypt)) + (home-page (package-home-page zephyr)) + (synopsis "Hello-world sample of the Zephyr Project") + (description "A simple Zephyr Project sample that prints \"Hello World\" to +the console.") + (license (package-license zephyr))))) + +(define*-public (make-zephyr-mcuboot-for-arm board extra-inputs + #:key + (configure-flags #~(list)) + (key (file-append zephyr-module-mcuboot + (zephyr-module-installation-target + zephyr-module-mcuboot) + "/main/root-rsa-2048.pem"))) + "Make an MCUboot bootloader package for Zephyr targeting the Arm +microcontroller BOARD. Add EXTRA-INPUTS to the build and use the list of +optional CONFIGURE-FLAGS. Use the public KEY file for firmware decryption." + (make-zephyr-application-for-arm + (package/inherit zephyr-module-mcuboot + (name (string-append "zephyr-mcuboot-" board)) + (source + (file-append zephyr-module-mcuboot + (zephyr-module-installation-target zephyr-module-mcuboot) + "/boot/zephyr")) + (arguments + (list + #:configure-flags + #~(append #$configure-flags + (list (string-append "-DBOARD=" #$board))))) + (inputs (append extra-inputs (list zephyr-module-mcuboot)))))) + +(define-public zephyr-mcuboot-nrf52840dongle_nrf52840 + (make-zephyr-mcuboot-for-arm "nrf52840dongle_nrf52840" + (list zephyr-module-hal-nordic) + #:configure-flags + #~(list "-DMCUBOOT_USE_MBED_TLS=y" + "-DCONFIG_LOG=y"))) + +(define-public imgtool + (package + (name "imgtool") + (version (package-version zephyr-module-mcuboot)) + (source (file-append zephyr-module-mcuboot + (zephyr-module-installation-target + zephyr-module-mcuboot) + "mcuboot/scripts")) + (build-system python-build-system) + (arguments + (list #:phases + #~(modify-phases %standard-phases + (add-after 'unpack 'disable-broken-tests + (lambda _ + (substitute* "imgtool/keys/ecdsa_test.py" + ;; This one is calling _unsupported, which raises an + ;; ECDSAUsageError. + (("def test_keygen") "def broken_test_keygen") + ;; This one is failing with an AttributeError. + (("def test_emit_pub") "def broken_test_emit_pub"))))))) + (propagated-inputs (list openssl-3.0 + python-cbor2 + python-click + python-cryptography + python-intelhex + python-pyyaml)) + (home-page (package-home-page zephyr-module-mcuboot)) + (synopsis "MCUboot's image signing and key management") + (description "A tool to securely sign firmware images for booting by +MCUboot.") + (license (package-license zephyr-module-mcuboot)))) diff --git a/zephyr/modules.scm b/zephyr/modules.scm new file mode 100644 index 0000000..5378e9d --- /dev/null +++ b/zephyr/modules.scm @@ -0,0 +1,223 @@ +(define-module (zephyr modules) + #:use-module (guix gexp) + #:use-module (guix build-system copy) + #:use-module (guix git-download) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix packages) + #:use-module (ice-9 regex) + #:use-module (zephyr)) + +(define-public (zephyr-module-installation-target zephyr-module) + "Return the target directory for the install-plan of the copy-build-system for +the ZEPHYR-MODULE package. It needs to match a certain pattern to collect +search-paths for zephyr-modules in the ZEPHYR_MOUDULES environment variable." + (string-append + ;; Add the needed prefix to the module name. + "/" %zephyr-module "/" %zephyr-module-tag + ;; Get the module name from the usually prefixed package name. + (regexp-substitute #f (string-match "^(zephyr-module-|)" + (package-name zephyr-module)) + "" 'post))) + +(define-public zephyr-module-template + "Return a template package to inherit zephyr module packages from. It +provides the build-system and proper arguments." + (package + (name "zephyr-module-template") + (version "0") + (source #f) + (build-system copy-build-system) + (arguments + (list #:install-plan + #~(list + (list "." #$(zephyr-module-installation-target this-package))))) + (home-page #f) + (synopsis #f) + (description #f) + (license #f))) + +(define-public zephyr-module-cmsis + (let ((revision "1") + (commit "74981bf893e8b10931464b9945e2143d99a3f0a3")) + (package/inherit zephyr-module-template + (name "zephyr-module-cmsis") + (version (git-version "5.8.0" revision commit)) + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/zephyrproject-rtos/cmsis") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "11wwcdwzi5ac8k881616p69v7cz356cwvbqsmmfw3w3v63b9dsmy")))) + (home-page "https://github.com/zephyrproject-rtos/cmsis") + (synopsis "Zephyr module for CMSIS") + (description "Zephyr module providing the Common Microcontroller +Software Interface Standard.") + (license license:apsl2)))) + +(define-public zephyr-module-hal-nordic + (let ((revision "1") + (commit "a1c3e0fbaafda091139b8744becd4853ada2f747")) + (package/inherit zephyr-module-template + (name "zephyr-module-hal-nordic") + (version (git-version "3.0.0" revision commit)) + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/zephyrproject-rtos/hal_nordic") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "1y0sz6m0fpfwxrsqrn29vvd5dxmk9804s4gwpxc9rdd79adklbab")))) + (home-page "https://github.com/zephyrproject-rtos/hal_nordic") + (synopsis "Zephyr module for Nordic Semiconductor's SoCs and SiPs") + (description "Zephyr module providing the Hardware Abstraction Layer for +Nordic Semiconductor's SoCs and SiPs. + +Supported SoCs and SiPs: +@itemize +@item nRF51 Series +@item nRF52805 +@item nRF52810 +@item nRF52811 +@item nRF52820 +@item nRF52832 +@item nRF52833 +@item nRF52840 +@item nRF5340 +@item nRF9131 +@item nRF9160 +@item nRF9161 +@end itemize +") + (license license:bsd-3)))) + +(define-public zephyr-module-hal-nordic-2.11 + (let ((revision "0") + (commit "5644a13252e5d12e3e841105d106cfdeb40e59f9")) + (package/inherit zephyr-module-hal-nordic + (name "zephyr-module-hal-nordic") + (version (git-version "2.11.0" revision commit)) + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/zephyrproject-rtos/hal_nordic") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "0lj7wlbfp9pb5pv819h9kbddmlzfbdnbmxhpm1i4xmf89z9v14sm"))))))) + +(define-public zephyr-module-hal-stm32 + (let ((revision "1") + (commit "c865374fc83d93416c0f380e6310368ff55d6ce2")) + (package/inherit zephyr-module-template + (name "zephyr-module-hal-stm32") + (version (git-version "1.16.0" revision commit)) + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/zephyrproject-rtos/hal_stm32") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "0q0ckial6a3lvlag44zm65dklbbdnqpzr1vbh85dhwx7acpjd5ni")))) + (home-page "https://github.com/zephyrproject-rtos/hal_stm32") + (synopsis "Zephyr module for STM32 microcontrollers") + (description "Zephyr module providing the required STM32cube packages, +dtsi files and libraries needed to build a Zephyr application running on STM32 +silicon.") + (license license:bsd-3)))) + +(define-public zephyr-module-lvgl + (let ((revision "1") + (commit "70a7849726be8375e3d941153dc417823ea7f355")) + (package/inherit zephyr-module-template + (name "zephyr-module-lvgl") + (version (git-version "8.2.0" revision commit)) ; Taken from lvgl.h. + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/zmkfirmware/lvgl") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "147mykkb72nwbjhrw4z7h0kkxw4p7kvy0w001s44rgplxhqqsg98")))) + (home-page "https://github.com/zmkfirmware/lvgl") + (synopsis "Zephyr module for LVGL") + (description "Zephyr module providing LVGL, the Light and Versatile +Graphics Library for an embedded GUI with graphical elements, visual effects +and a low memory footprint.") + (license license:apsl2)))) + +(define-public zephyr-module-mbedtls + (let ((revision "1") + (commit "711fd5ea13a5e018cebe1ac1f79c22d897e8b34d")) + (package/inherit zephyr-module-template + (name "zephyr-module-mbedtls") + (version (git-version "3.3.0" revision commit)) + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/zephyrproject-rtos/mbedtls") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "0lvvarwx735g2bc272n03gp8dsx0bn0i4cywmi6igrc6dyqwkabq")))) + (home-page "https://github.com/zephyrproject-rtos/mbedtls") + (synopsis "Zephyr module for Mbed TLS") + (description "Zephyr module providing Mbed TLS, a C library that +implements cryptographic primitives, X.509 certificate manipulation and the +SSL/TLS and DTLS protocols. Its small code footprint makes it suitable for +embedded systems.") + (license license:apsl2)))) + +(define-public zephyr-module-mcuboot + (let ((revision "1") + (commit "74c4d1c52fd51d07904b27a7aa9b2303e896a4e3")) + (package/inherit zephyr-module-template + (name "zephyr-module-mcuboot") + (version (git-version "1.11.0-dev" revision commit)) + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/zephyrproject-rtos/mcuboot") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "14g95wjqxchv2vnpzld8in6ljlw6s2cj43nlyf6g8czyy5rx99qb")))) + (home-page "https://github.com/zephyrproject-rtos/mcuboot") + (synopsis "Zephyr module for MCUboot") + (description "Zephyr module providing the secure bootloader MCUboot for +32-bit microcontrollers. It defines a common infrastructure for the bootloader +and the system flash layout on microcontroller systems, and provides a secure +bootloader that enables easy software upgrade.") + (license license:apsl2)))) + +(define-public zephyr-module-tinycrypt + (let ((revision "1") + (commit "3e9a49d2672ec01435ffbf0d788db6d95ef28de0")) + (package/inherit zephyr-module-template + (name "zephyr-module-tinycrypt") + (version (git-version "0.2.8" revision commit)) + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/zephyrproject-rtos/tinycrypt") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "19d2q9y23yzz9i383q3cldjl3k5mryx9762cab23zy3ijdnmj2z6")))) + (home-page "https://github.com/zephyrproject-rtos/tinycrypt") + (synopsis "Zephyr module for the TinyCrypt library") + (description "Zephyr module providing the TinyCrypt library.") + (license (license:non-copyleft "file://README"))))) + diff --git a/zephyr/zmk.scm b/zephyr/zmk.scm new file mode 100644 index 0000000..0bbceca --- /dev/null +++ b/zephyr/zmk.scm @@ -0,0 +1,504 @@ +(define-module (zephyr zmk) + #:use-module (guix build union) + #:use-module (guix build utils) + #:use-module (guix build-system trivial) + #:use-module (guix gexp) + #:use-module (guix git-download) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix packages) + #:use-module (ice-9 match) + #:use-module (ice-9 optargs) + #:use-module (srfi srfi-1) + #:use-module (zephyr) + #:use-module (zephyr apps) + #:use-module (zephyr modules)) + +(define zmk-config + (package + (name "zmk-config") + (version "0") + (source #f) + (build-system trivial-build-system) + (arguments (list #:builder #~(mkdir #$output))) + (native-search-paths + (list + (search-path-specification + (variable "ZMK_CONFIG") + (files '("zmk-config")) + (separator #f) + (file-type 'directory) + (file-pattern "^config$")))) + (home-page "https://zmk.dev/docs/config#config-file-locations") + (synopsis "ZMK firmware configuration") + (description "This ZMK Firmware configuration is a helper to set the +ZMK_CONFIG environment varibale during a ZMK Firmware package build to its +configuration input. Add a file-like object like a file-union or a package +containing a zmk-config/config folder as build input to a ZMK Firmare packege.") + (license license:expat))) + + +(define*-public (make-zmk board + #:key + (shield "") + (extra-inputs '()) + (extra-name "") + (patches '()) + snippet) + "Make a ZMK firmware package for a keyboard consisting of an Arm +microcontroller BOARD with a SHIELD PCB using the list of EXTRA-INPUTS. Add an +EXTRA-NAME with a trailing hyphen to customize the package name. Use PATCHES or +SNIPPET to modify the ZMK sources." + (make-zephyr-application-for-arm + (let* ((revision "1") + (commit "9d714c0b69fee2098a010d29e534051aeca26386") + (underscore->hyphen (lambda (name) + (string-map (lambda (char) + (if (char=? char #\_) + #\- + char)) + name))) + (board-name (underscore->hyphen board)) + (shield-name (underscore->hyphen shield)) + (shield (if (string-null? shield) #f shield))) + (package + (name (string-append shield-name (if shield "-" "") + extra-name board-name "-zmk")) + (version (git-version "2023.06.12" revision commit)) + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/zmkfirmware/zmk") + (commit commit))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "08mihhcdlb9hh1qa0l6limggmvy98qiq6051p9qhnh6zbs8021h7")) + (patches patches) + (snippet snippet))) + (build-system #f) + (arguments + (list + #:out-of-source? #t + #:configure-flags + #~(append (list "-S../source/app" + (string-append "-DBOARD=" #$board)) + (if #$shield + (list (string-append "-DSHIELD=" #$shield)) + '())))) + (inputs (append extra-inputs + (list zephyr-module-cmsis + zephyr-module-lvgl + zephyr-module-tinycrypt + zmk-config))) + (home-page "https://zmk.dev") + (synopsis (if shield (format #f "ZMK Firmware for a ~a keyboard with ~a" + shield-name board-name) + (format #f "ZMK Firmware for a ~a keyboard" + board-name))) + (description "ZMK Firmware is an open source (MIT) keyboard firmware +built on the Zephyr™ Project Real Time Operating System (RTOS).") + (license license:expat))) + #:zephyr zephyr-3.2+zmk-fixes + #:source-prefix "zmk")) + +(define*-public (make-nrfmicro-13-zmk shield #:key zmk-config (extra-name "")) + "Make a ZMK firmware package for a keyboard consisting of the nrfmicro 1.3/1.4 +board with a SHIELD PCB. Use the ZMK-CONFIG directory containing optional +boards/ or dts/ directories, or .conf, .keypad, .overlay files prefixed with +shield or board names." + (make-zmk + "nrfmicro_13" + #:shield shield + #:extra-name extra-name + #:extra-inputs (append (list zephyr-module-hal-nordic-2.11) + (if zmk-config (list zmk-config) + '())) + #:snippet + #~(begin + (use-modules (guix build utils)) + (substitute* "app/CMakeLists.txt" + ;; Move combo.c and behaviour_tap_dance.c above all other behaviors. + (("^ target_sources\\(app PRIVATE src/combo.c\\)\n") "") + (("^ target_sources\\(app PRIVATE src/behaviors/behavior_tap_dance.c\\)\n") + "") + (("^ target_sources\\(app PRIVATE src/hid.c\\)\n" line) + (string-append + line + " target_sources(app PRIVATE src/combo.c)\n" + " target_sources(app PRIVATE src/behaviors/behavior_tap_dance.c)\n")))))) + +(define*-public (make-zmk-union zmk-packages #:key name synopsis) + "Make a union of several ZMK Firmware packages for left and right hand or +settings-reset firmware files." + (package + (inherit (car zmk-packages)) + (name (or name (package-name (car zmk-packages)))) + (source #f) + (build-system trivial-build-system) + (arguments + (list #:modules '((guix build union)) + #:builder + #~(begin + (use-modules ((guix build union))) + (union-build #$output (quote #$zmk-packages))))) + (synopsis (or synopsis (package-synopsis (car zmk-packages)))))) + +(define (hid-modifier modifier) + (define hid-modifier->zmk-macro + '((⇧ . LS) (⌃ . LC) (⌥ . LA) (⌘ . LG) + (R⌘ . RG) (R⌥ . RA) (R⌃ . RC) (R⇧ . RS))) + (or (assoc-ref hid-modifier->zmk-macro modifier) modifier)) + +(define-public (special-bindings key-label) + (define special-bindings->zmk-name + '(;; A whole in the keyboard matrix without meaning to ZMK. + (◌ . "") + ;; No functionality. + (☒ . &none) + ;; Fall-through to the next active lower layer. + (☐ . &trans) + ;; Keypress on sensor, requires two parameters for up and down keycodes. + (⟳ . &inc_dec_kp) + ;; Reset and bootloader, on split keyboards this is side specific. + (⎊ . &sys_reset) (↯ . &bootloader) + ;; Bluetooth, requires one or two parameters. + (⌔ . &bt) + ;; Backlight, requires one parameter. + (☼ . &bl))) + (or (assoc-ref special-bindings->zmk-name key-label) key-label)) + +(define-public (hid key-label) + (define hid->zmk-name + '((⎋ . ESC) (⎙ . PSCRN) (⤓ . SLCK) (⎉ . PAUSE_BREAK) + (^ . GRAVE) (- . MINUS) + (= . EQUAL) (⌫ . BSPC) + (⇥ . TAB) (⟦ . LBKT) (⟧ . RBKT) (↲ . RET) (⏎ . RET) (↩ . RET) + (⇪ . CAPS) (⍮ . SEMI) (⍘ . SQT) (⋕ . NUHS) + (⇧ . LSHFT) (\ . NUBS) + (‚ . COMMA) (· . DOT) (/ . SLASH) (R⇧ . RSHFT) + (⌃ . LCTRL) (⌥ . LALT) (⌘ . LGUI) (␣ . SPC) + (R⌘ . RGUI) (R⌥ . RALT) (R⌃ . RCTRL) (☰ . K_APP) + (⌵ . INS) (⇱ . HOME) (↖ . HOME) (⇞ . PG_UP) + (⌦ . DEL) (⇲ . END) (↘ . END) (⇟ . PG_DN) + (← . LEFT) (↓ . DOWN) (↑ . UP) (→ . RIGHT) + (⇠ . LEFT) (⇣ . DOWN) (⇡ . UP) (⇢ . RIGHT) + (⇭ . KP_NUMLOCK) (NUM . KP_NUMLOCK) + (⌧ . KP_CLEAR) (⟨ . KP_LPAR) (⟩ . KP_RPAR) (P= . KP_EQUAL) + (÷ . KP_DIVIDE) (* . KP_MULTIPLY) (− . KP_MINUS) (+ . KP_PLUS) + (P1 . KP_N1) (P2 . KP_N2) (P3 . KP_N3) (P4 . KP_N4) (P5 . KP_N5) + (P6 . KP_N6) (P7 . KP_N7) (P8 . KP_N8) (P9 . KP_N9) (P0 . KP_N0) + (P. . KP_DOT) (P, . KP_COMMA) (⌤ . ENTER) + (✄ . C_AC_CUT) (◫ . C_AC_COPY) (⎀ . C_AC_PASTE) + (↶ . C_AC_UNDO) (↷ . C_AC_REDO) + (⌨ . C_AL_KEYBOARD_LAYOUT))) + (special-bindings (or (assoc-ref hid->zmk-name key-label) key-label))) + +(define-public (de key-label) + (define de->hid + '((ß . -) (´ . =) + (Z . Y) (Ü . ⟦) (+ . ⟧) + (Ö . ⍮) (Ä . ⍘) + (< . \) (Y . Z) (- . /) + (P+ . +) (P, . P.) (P. . P,))) + (hid (or (assoc-ref de->hid key-label) key-label))) + +(define-public (neo key-label) + (define neo->de + '((T1 . ^) + (X . Q) (V . W) (L . E) (C . R) (W . T) + (M3 . ⇪) (U . A) (I . S) (A . D) (E . F) (O . G) + (M4 . <) (Ü . Y) (Ö . X) (Ä . C) (P . V) (Z . B) + (- . ß) (T2 . ´) + (K . Z) (H . U) (G . I) (F . O) (Q . P) (ẞ . Ü) (T3 . +) + (S . H) (N . J) (R . K) (T . L) (D . Ö) (Y . Ä) (RM3 . ⋕) + (B . N) (J . -) (RM4 . R⌥) + (P⇥ . ⇭))) + (de (or (assoc-ref neo->de key-label) key-label))) + +(define*-public (zmk-keymap #:key (properties '()) + (behaviors '()) + (combos '()) + (conditional_layers '()) + (layers '()) + (macros '())) + "Generate the content of a keymap file for ZMK. Each layer in LAYERS has a +name, a layout and multiple rows, of which each contains the key-bindings. The +last row contains the bindings for sensors. The key-bindings use symbols on +LAYOUT. The BEHAVIORS, COMBOS, MACROS and CONDITIONAL-LAYERS contain lists of +strings to inject own appropiate definitions for ZMK. PROPERTIES may contain +properties for behaviors or even C macro definitions." + (define (include file) + "Return an include statement for file" + (string-append "#include <" file ">")) + (define (include-binding file) + "Return an include statement for file defining bindings." + (include (string-append "dt-bindings/zmk/" file))) + (define (includes) + "Return all include statements offered by ZMK for keymap files." + (append (map include '("behaviors.dtsi")) + (map include-binding '("backlight.h" "bt.h" "ext_power.h" + "hid_usage.h" "hid_usage_pages.h" "keys.h" + "kscan_mock.h" "matrix_transform.h" + "modifiers.h" "outputs.h" "reset.h" + "rgb.h")))) + + (define* (keymap-layer name layout rows) + "Return a string with a keymap layer definition NAME for a ZMK keymap file, +consisting of KEYS with their labels based on LAYOUT." + + (define (zmk-name->string zmk-name) + "Tansform a ZMK-NAME into a string." + (cond ((string? zmk-name) zmk-name) + ((number? zmk-name) (number->string zmk-name)) + (else (symbol->string zmk-name)))) + + (define (key-label->zmk key-label) + "Tansform a key-label based on a keyboard-layout into a ZMK string." + (zmk-name->string (layout key-label))) + + (define (modified-key->zmk modified-key) + "Transform a possibly MODIFIED-KEY like '(⇧ ⌥ ⎋) into the \"LS((LA(ESC))\" +respresentation of ZMK." + (match modified-key + ((modifier modifier-or-key . rest) + (string-append (zmk-name->string (hid-modifier modifier)) + "(" + (modified-key->zmk (cdr modified-key)) + ")")) + ((unmodified-key) + (modified-key->zmk unmodified-key)) + (key-label + (key-label->zmk key-label)))) + + (define (behavior->zmk behavior strings-of-layers-and-modified-keys) + "Join a BEHAVIOR symbol like '&mt with STRINGS-OF-LAYERS-AND-MODIFIED-KEYS +as parameters like '(\"LALT\" \"ESC\") into the \"&mt LALT ESC\" respresentation +of ZMK." + (string-join (cons (key-label->zmk behavior) + strings-of-layers-and-modified-keys))) + + (define (&-symbol? symbol) + "Predicate to identify a symbol as a ZMK behavior prefixed with &." + (string=? "&" (string-take (key-label->zmk symbol) 1))) + + (define (key-binding->zmk key-binding) + "Transform the KEY-BINDING, which could be a key-label, a modified key, or +a behavior with layer and modified key parameters, into the representation of a +ZMK behavior for a keymap layer." + (match key-binding + (((? &-symbol? behavior) . parameters) + ;; A list starting with an &-symbol is a behavior with parameters. + ;; The parameters themselves may be layers or modified keys. + (behavior->zmk behavior (map modified-key->zmk parameters))) + (modified-key + (let ((modified-key (modified-key->zmk modified-key))) + (if (or (string-null? modified-key) + (&-symbol? modified-key)) + ;; There is nothing or a behavior is present, just use it. + modified-key + ;; Add a key-press behavior to the modified-key and start over. + (behavior->zmk '&kp (list modified-key))))))) + + (define (keys->zmk key-bindings) + "Transform a list of KEY-BINDINGS into ZMK behaviors for a keymap layer." + (string-join (map (lambda (zmk-behavior) + (string-pad-right + zmk-behavior + (max 12 (string-length zmk-behavior)))) + (map key-binding->zmk key-bindings)))) + + (string-append " " name "_layer {" + "\n bindings = <" + (string-join (map keys->zmk (drop-right rows 1)) + "\n " 'prefix) + "\n >;" + (if (null? (last rows)) + "" + (string-append + "\n sensor-bindings = <" + (string-join (map keys->zmk (last rows)) + "\n " 'prefix) + "\n >;")) + "\n };")) + + (define (layer layer) + "Return a string for a ZMK keymap file containing a layer definition." + (match layer + ((name layout . rows) + (keymap-layer name layout rows)))) + + (string-join (append (includes) + properties + (list "/ {" + " behaviors {") + behaviors + (list " };" + " combos {" + " compatible = \"zmk,combos\";") + combos + (list " };" + " conditional_layers {" + " compatible = \"zmk,conditional_layers\";") + conditional_layers + (list " };" + " keymap {" + " compatible = \"zmk,keymap\";") + (map layer layers) + (list " };" + " macros {") + macros + (list " };" + "};")) + "\n")) + +;; This is a hold-tap behavior for a key, which momentarily activates a layer, +;; if hold, or switches to that layer, if tapped. +(define-public layer-hold-tap +" /omit-if-no-ref/ lht: behavior_layer_hold_tap { + compatible = \"zmk,behavior-hold-tap\"; + label = \"LAYER_HOLD_TAP\"; + #binding-cells = <2>; + flavor = \"balanced\"; + tapping-term-ms = <200>; + bindings = <&mo>, <&to>; + }; +") + +(define-public (layer-tap-dance n) + "Give a tap-dance behavior '<dN', which counts the taps for the layer number +and momentarily activates that layer on hold, or switches to that layer on tap. +If the parameter N is 0, then taps select the layers 1, 2, 3. If N is 1, taps +select the layers 0, 2, 3, and so on." + (let ((first (if (>= n 1) "0 0" "1 1")) + (second (if (>= n 2) "1 1" "2 2")) + (third (if (>= n 3) "2 2" "3 3")) + (n (number->string n))) + (string-append +" /omit-if-no-ref/ ltd" n ": behavior_layer_tap_dance" n " { + compatible = \"zmk,behavior-tap-dance\"; + label = \"LAYER_TAP_DANCE" n "\"; + #binding-cells = <0>; + tapping-term-ms = <200>; + bindings = <&lht " first ">, <&lht " second ">, <&lht " third ">; + }; +"))) + +(define-public settings-reset-nrfmicro-13-zmk + (package + (inherit (make-nrfmicro-13-zmk "settings_reset")) + (synopsis "ZMK settings reset firmware for split-keyboards with nrfmicro +1.3/1.4 boards") + (description "Pairing issues of ZMK firmware split-keyboard halves can be +resolved by flashing this settings reset firmware to both controllers."))) + +(define-public redox-left-nrfmicro-13-zmk + (make-nrfmicro-13-zmk "redox_left")) + +(define-public redox-right-nrfmicro-13-zmk + (make-nrfmicro-13-zmk "redox_right")) + +(define-public redox-nrfmicro-13-zmk + (make-zmk-union + (list settings-reset-nrfmicro-13-zmk + redox-left-nrfmicro-13-zmk + redox-right-nrfmicro-13-zmk) + #:name "redox-nrfmicro-13-zmk" + #:synopsis "ZMK firmware for a Redox shield with nrfmicro-1.3/1.4 board")) + +(define-public redox-neo-keymap + (let* ((M3Y '(&mt RM3 Y)) + (⇧- '(&mt ⇧ -)) + (R⇧ẞ '(&mt R⇧ ẞ)) + (⌥- '(&mt ⌥ -)) + (⌥. '(&mt ⌥ ·)) + (l1␣ '(< 1 ␣)) + (to0 '(&to 0)) ; Switch to layer 0. + (l0 '(<d0)) ; Layer dancing for layer 0. + (l1 '(<d1)) ; Layer dancing for layer 1. + (l2 '(<d2)) ; Layer dancing for layer 2. + (l3 '(<d3)) ; Layer dancing for layer 3. + (⌔1 '(⌔ BT_SEL 0)) + (⌔2 '(⌔ BT_SEL 1)) + (⌔3 '(⌔ BT_SEL 2)) + (⌔4 '(⌔ BT_SEL 3)) + (⌔5 '(⌔ BT_SEL 4)) + (⌔⌧ '(⌔ BT_CLR)) + (⌔→ '(⌔ BT_NXT)) + (⌔← '(⌔ BT_PRV)) + (keymap + (zmk-keymap + #:layers + `(("default" ,neo + ( ⎋ N1 N2 N3 N4 N5 ◌ ◌ ◌ ◌ N6 N7 N8 N9 N0 ⌫ ) + ( ⇥ X V L C W T2 ◌ ◌ ☰ K H G F Q ↲ ) + ( M3 U I A E O T3 ◌ ◌ ⌵ S N R T D ,M3Y) + (,⇧- Ü Ö Ä P Z ⇧ ⌘ R⌘ R⇧ B M ‚ · J ,R⇧ẞ) + ( ⌃ ⌥ T1 ⌦ ,l0 ◌ ⌃ M4 RM4 R⌃ ◌ ,l1␣ ⌦ ,l0 ,⌥- R⌃ ) + ()) + ("cursor" ,neo + ( ⎋ F1 F2 F3 F4 F5 ◌ ◌ ◌ ◌ F6 F7 F8 F9 F10 ⌫ ) + ( ⇥ ⇞ ⌫ ↑ ⌦ ⇟ ⎉ ◌ ◌ ☒ ⇞ ⌫ ↑ ⌦ ⇟ ↲ ) + ( ☒ ⇱ ← ↓ → ⇲ ☒ ◌ ◌ ☒ ⇱ ← ↓ → ⇲ ☒ ) + (,⇧- ⎋ ⇥ ⎀ ↲ ↶ ⇧ ⌘ R⌘ ⇧ ⎋ ⇥ ⎀ ↲ ↶ R⇧ ) + ( ⌃ ⌥ ⎙ ⌦ ,l1 ◌ ⌃ ⌥ ⌥ R⌃ ◌ ␣ ⌦ ,l1 ,⌥- R⌃ ) + ()) + ("keypad" ,neo + ( ⎋ F11 F12 F13 F14 F15 ◌ ◌ ◌ ◌ ⎋ P⇥ ÷ * − ⌫ ) + ( ⇥ ⇞ ⌫ ↑ ⌦ ⇟ ☒ ◌ ◌ ⇭ ☒ P7 P8 P9 P+ ↲ ) + ( M3 ⇱ ← ↓ → ⇲ ☒ ◌ ◌ ☒ ☒ P4 P5 P6 P= M3 ) + (,⇧- ⎋ ⇥ ⎀ ↲ ↶ ⇧ ⌘ R⌘ R⇧ ␣ P1 P2 P3 ⌤ R⇧ ) + ( ⌃ ⌥ ☒ ⌦ ,l2 ◌ ⌃ M4 RM4 R⌃ ◌ P0 ⌦ P, ,⌥. R⌃ ) + ()) + ("zmk" ,neo + ( ⎊ ,⌔1 ,⌔2 ,⌔3 ,⌔4 ,⌔5 ◌ ◌ ◌ ◌ ☒ ☒ ☒ ☒ ☒ ⎊ ) + ( ↯ ☒ ☒ ☒ ☒ ☒ ☒ ◌ ◌ ☒ ☒ ☒ ☒ ☒ ☒ ↯ ) + ( ☒ ☒ ,⌔← ,⌔⌧ ,⌔→ ☒ ☒ ◌ ◌ ☒ ☒ ☒ ☒ ☒ ☒ ☒ ) + ( ☒ ☒ ☒ ☒ ☒ ☒ ☒ ☒ ☒ ☒ ☒ ☒ ☒ ☒ ☒ ☒ ) + ( ☒ ☒ ☒ ☒ ,l3 ◌ ☒ ☒ ☒ ☒ ☒ ☒ ☒ ,l3 ☒ ☒ ) + ())) + #:properties (list "< {quick-tap-ms = <200>;};" + "&mt {quick-tap-ms = <200>;};") + #:combos (list " combo_up {" ; G F ⇒ ↑ + " key-positions = <22 23>;" + " bindings = <&kp UP>;" + " };" + " combo_left {" ; N R ⇒ ← + " key-positions = <35 36>;" + " bindings = <&kp LEFT>;" + " };" + " combo_down {" ; R T ⇒ ↓ + " key-positions = <36 37>;" + " bindings = <&kp DOWN>;" + " };" + " combo_right {" ; T D ⇒ → + " key-positions = <37 38>;" + " bindings = <&kp RIGHT>;" + " };") + #:behaviors (list layer-hold-tap + (layer-tap-dance 0) + (layer-tap-dance 1) + (layer-tap-dance 2) + (layer-tap-dance 3))))) + (file-union "redox-config" + (list (list "zmk-config/config/redox.keymap" + (plain-file "redox-neo.keymap" keymap)))))) + +(define-public redox-left-neo-nrfmicro-13-zmk + (make-nrfmicro-13-zmk "redox_left" + #:zmk-config redox-neo-keymap + #:extra-name "neo-")) + +(define-public redox-right-neo-nrfmicro-13-zmk + (make-nrfmicro-13-zmk "redox_right" + #:zmk-config redox-neo-keymap + #:extra-name "neo-")) + +(define-public redox-neo-nrfmicro-13-zmk + (make-zmk-union + (list settings-reset-nrfmicro-13-zmk + redox-left-neo-nrfmicro-13-zmk + redox-right-neo-nrfmicro-13-zmk) + #:name "redox-neo-nrfmicro-13-zmk" + #:synopsis + "Neo layout ZMK firmware for a Redox shield with nrfmicro-1.3/1.4 board")) +