diff --git a/gnu/packages/firmware.scm b/gnu/packages/firmware.scm
index 1957fb8aac4..7220177eadb 100644
--- a/gnu/packages/firmware.scm
+++ b/gnu/packages/firmware.scm
@@ -28,6 +28,7 @@
 
 (define-module (gnu packages firmware)
   #:use-module ((guix licenses) #:prefix license:)
+  #:use-module (guix memoization)
   #:use-module (guix packages)
   #:use-module (guix download)
   #:use-module (guix gexp)
@@ -43,6 +44,7 @@ (define-module (gnu packages firmware)
   #:use-module (gnu packages admin)
   #:use-module (gnu packages autotools)
   #:use-module (gnu packages avr)
+  #:use-module (gnu packages avr-xyz)
   #:use-module (gnu packages assembly)
   #:use-module (gnu packages backup)
   #:use-module (gnu packages base)
@@ -80,7 +82,10 @@ (define-module (gnu packages firmware)
   #:use-module (gnu packages tls)
   #:use-module (gnu packages version-control)
   #:use-module (gnu packages xml)
-  #:use-module (ice-9 match))
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+
+  #:export (make-qmk-firmware))
 
 (define-public ath9k-htc-firmware
   (package
@@ -1308,3 +1313,122 @@ (define-public qmk
 install the udev rules provided by the @code{qmk-udev-rules} package to avoid
 having to run @command{qmk} as root when flashing the firmware.")
     (license license:expat)))
+
+(define* (make-qmk-firmware/implementation keyboard keymap
+                                           #:key (description "")
+                                           keymap-json
+                                           keymap-source-directory)
+  "Return a package to build the QMK firmware for KEYBOARD with KEYMAP.
+Keyboard should be the name of a sub-directory under the @file{keyboards}
+directory.  For custom keymaps, KEYMAP-JSON, a file-like object of a JSON
+representation of KEYMAP as generated by the @url{https://config.qmk.fm/, QMK
+Configurator} tool or KEYMAP-SOURCE-DIRECTORY, a file-like object directory
+containing the keymap source files files such as @file{keymap.c}, can be
+provided."
+  (package
+    (name (string-append "qmk-firmware-"
+                         (string-replace-substring keyboard "_" "-") "-"
+                         (string-replace-substring keymap "_" "-")))
+    ;; Note: When updating this package, make sure to also update the commit
+    ;; used for the LUFA submodule in the 'copy-lufa-source' phase below.
+    (version "0.22.3")
+    (source (origin
+              (method git-fetch)
+              (uri (git-reference
+                    (url "https://github.com/qmk/qmk_firmware")
+                    (commit version)))
+              (file-name (git-file-name "qmk-firmware" version))
+              (sha256
+               (base32
+                "0s1lcnv7cddpn768p7mrc5bkxhx0ba5p77ya007dnkbk36c33d0w"))))
+    (build-system gnu-build-system)
+    (arguments
+     (list
+      #:modules '((guix build gnu-build-system)
+                  (guix build utils)
+                  (ice-9 match)
+                  (srfi srfi-26))
+      ;; XXX: Running a test target like "test:$keyboard" doesn't seem to run
+      ;; anything and causes the .hex file to be regenerated; leave the tests
+      ;; out for now.
+      #:tests? #f
+      #:make-flags
+      #~(list #$(format #f "~a:~a" keyboard keymap)
+              (string-append "SHELL=" (search-input-file
+                                       %build-inputs "bin/sh")))
+      #:phases
+      #~(modify-phases %standard-phases
+          (replace 'configure
+            (lambda _
+              ;; Do not attempt to retrieve information from git during the
+              ;; build.
+              (setenv "SKIP_GIT" "1")))
+          (add-after 'unpack 'copy-lufa-source
+            ;; QMK carries a custom fork of LUFA as a git submodule; make sure
+            ;; the same commit is used (see:
+            ;; https://github.com/qmk/qmk_firmware/tree/master/lib).
+            (lambda _
+              (copy-recursively
+               #$(let ((commit "549b97320d515bfca2f95c145a67bd13be968faa"))
+                   (origin
+                     (inherit (package-source lufa))
+                     (uri (git-reference
+                           (url "https://github.com/qmk/lufa")
+                           (commit commit)))
+                     (file-name (git-file-name "lufa" commit))
+                     (sha256
+                      (base32
+                       "1rmhm4rxvq8skxqn6vc4n4ly1ak6whj7c386zbsci4pxx548n9h4"))))
+               "lib/lufa")))
+          #$@(if keymap-source-directory
+                 #~((add-after 'unpack 'copy-keymap-source-directory
+                      (lambda _
+                        (let ((keymap-dir #$(string-append "keyboards/" keyboard
+                                                           "/keymaps/" keymap)))
+                          (false-if-exception (delete-file-recursively
+                                               keymap-dir))
+                          (copy-recursively #$keymap-source-directory
+                                            keymap-dir)))))
+                 #~())
+          #$@(if keymap-json
+                 #~((replace 'build
+                      (lambda _
+                        (invoke "qmk" "compile" #$keymap-json))))
+                 #~())
+          (replace 'install
+            (lambda _
+              (match (find-files ".build" "\\.hex$")
+                (()
+                 (error "no built binary file found"))
+                ((hex ..1)
+                 (for-each (cut install-file <> #$output) hex))))))))
+    ;; Some of the build tools are required to be on the PATH, as the make
+    ;; files do not always operate through 'qmk'; all of qmk's inputs must
+    ;; thus be made available.
+    (native-inputs (modify-inputs (package-inputs qmk)
+                     (append qmk)))
+    (home-page "https://qmk.fm/")
+    (synopsis "Keyboard firmware for Atmel AVR and Arm USB families")
+    (description
+     (format #f "QMK (Quantum Mechanical Keyboard Firmware) is a keyboard
+firmware based on the @url{https://github.com/tmk/tmk_keyboard, tmk_keyboard
+firmware} with some useful features for Atmel AVR and ARM controllers, and
+more specifically, the @url{https://olkb.com/, OLKB product line}, the
+@url{https://ergodox-ez.com/, ErgoDox EZ keyboard}, and the
+@url{https://clueboard.co/, Clueboard product line}.~@[~%~%~a~]" description))
+    (license license:gpl2+)))
+
+(define make-qmk-firmware (memoize make-qmk-firmware/implementation))
+
+(define-public qmk-firmware-ergodox-ez-default
+  (make-qmk-firmware
+   "ergodox_ez" "default" #:description
+   "This is the default keymap used on the ErgoDox EZ keyboard.  It includes
+the novel MEH and Hyper keys pioneered by the ErgoDox EZ, easy to reach
+Control/Shift modifiers keys, and two-functions hold keys to access layer 1.
+Layer 1 contains function keys, symbols, a numpad as well as brightness keys.
+Layer 2 contains multimedia and mouse keys.  See the
+@file{keyboards/ergodox_ez/keymaps/default/keymap.c} source file for the
+keymap definition, or the
+@url{https://configure.ergodox-ez.com/ergodox-ez/layouts/JwwW/latest/0,
+ErgoDox EZ Configurator} page."))
