{ config, lib, pkgs, namespace, ... }: let cfg = config.${namespace}.hardware.raspberry-pi; dt_ao_overlay = _final: prev: { deviceTree = prev.deviceTree // { applyOverlays = _final.callPackage ./apply-overlays-dtmerge.nix { }; }; }; ubootBinName = "u-boot.bin"; deviceTree = ({ pkgs , firmware }: pkgs.replaceVarsWith { src = ./generational/install-device-tree.sh; isExecutable = true; replacements = { inherit (pkgs) bash; path = pkgs.lib.makeBinPath [ pkgs.coreutils ]; inherit firmware; }; }); # installs raspberry's firmware independent of the nixos generations # sometimes referred to as "boot code" raspberryPiFirmware = ({ pkgs , firmware , configTxt }: pkgs.replaceVarsWith { src = ./generational/install-firmware.sh; isExecutable = true; replacements = { inherit (pkgs) bash; path = pkgs.lib.makeBinPath [ pkgs.coreutils ]; inherit firmware configTxt; }; }); # Builders used to write during system activation firmwareBuilder = import ./firmware-builder.nix { inherit pkgs; configTxt = pkgs.writeTextFile { name = "config.txt"; text = '' # Do not edit! # This configuration file is generated from NixOS configuration # options `hardware.raspberry-pi.config`. # Any manual changes will be overwritten on the next configuration # switch. ${config.${namespace}.hardware.raspberry-pi.config-generated} ''; }; firmware = pkgs.${namespace}.raspberrypifw; }; ubootBuilder = import ./uboot-builder.nix { inherit pkgs; ubootPackage = pkgs.${namespace}.uboot-pi5; firmwareBuilder = firmwarePopulateCmd; extlinuxConfBuilder = config.boot.loader.generic-extlinux-compatible.populateCmd; }; # Builders exposed via populateCmd, which run on the build architecture populateFirmwareBuilder = import ./firmware-builder.nix { pkgs = pkgs.buildPackages; configTxt = pkgs.writeTextFile { name = "config.txt"; text = '' # Do not edit! # This configuration file is generated from NixOS configuration # options `hardware.raspberry-pi.config`. # Any manual changes will be overwritten on the next configuration # switch. ${config.${namespace}.hardware.raspberry-pi.config-generated} ''; }; firmware = pkgs.${namespace}.raspberrypifw; }; populateUbootBuilder = import ./uboot-builder.nix { inherit ubootBinName; pkgs = pkgs.buildPackages; ubootPackage = pkgs.${namespace}.uboot-pi5; firmwareBuilder = firmwarePopulateCmd; extlinuxConfBuilder = config.boot.loader.generic-extlinux-compatible.populateCmd; }; firmwarePopulateCmd = "${populateFirmwareBuilder} ${firmwareBuilderArgs}"; firmwareBuilderArgs = lib.optionalString (!true) " -r"; # these will receive the top-level path as an argument when invoked as # system.build.installBootloader builder = { # system.build.installBootLoader uboot = "${ubootBuilder} -f /boot/firmware -b /boot -c"; }; # firmware: caller must provide `-c ` and `-f ` # boot: caller must provide `-c ` and `-b ` populateCmds = { uboot = { firmware = "${populateUbootBuilder}"; boot = "${populateUbootBuilder}"; }; }; in { options.${namespace}.hardware.raspberry-pi = { enable = lib.mkEnableOption "Raspberry Pi common configuration"; variant = lib.mkOption { type = lib.types.enum [ "4" "5" ]; description = "Raspberry Pi variant (4 or 5)"; }; apply-overlays-dtmerge = { enable = lib.mkEnableOption "" // { description = '' Whether replace deviceTree.applyOverlays implementation to use dtmerge from libraspberrypi. This can resolve issues with applying dtbs for the pi. ''; }; }; }; imports = [ ./audio.nix ./bluetooth.nix ./config.nix ./i2c.nix ./leds.nix ./modesetting.nix ./pwm.nix ./wifi.nix ]; config = lib.mkIf cfg.enable { boot = { initrd.availableKernelModules = [ "usbhid" "usb-storage" ] ++ (if (cfg.variant == "5") then [ "nvme" ] else [ "vc4" "pcie-brcmstb" # required for the pcie bus to work "reset-raspberrypi" # required for vl805 firmware to load ]); loader = { generic-extlinux-compatible = { enable = lib.mkDefault true; useGenerationDeviceTree = true; }; grub.enable = lib.mkForce false; }; }; # Common Raspberry Pi packages environment.systemPackages = with pkgs; [ i2c-tools raspberrypi-eeprom pkgs.${namespace}.raspberrypifw pkgs.${namespace}.raspberryPiWirelessFirmware raspberrypi-armstubs erofs-utils fex squashfuse squashfsTools ]; # Common Bluetooth configuration systemd = { services.btattach = { before = [ "bluetooth.service" ]; after = [ "dev-ttyAMA0.device" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = "${lib.getExe' pkgs.bluez "btattach"} -B /dev/ttyAMA0 -P bcm -S 3000000"; }; }; tmpfiles.packages = [ pkgs.${namespace}.udev-rules ]; }; # Common hardware settings hardware = { deviceTree = { filter = lib.mkDefault (if (cfg.variant == "5") then "bcm2712-rpi-*.dtb" else "bcm2711-rpi-*.dtb"); overlays = (if (cfg.variant == "4") then [ { name = "rpi4-cpu-revision"; dtsText = '' /dts-v1/; /plugin/; / { compatible = "raspberrypi,4-model-b"; fragment@0 { target-path = "/"; __overlay__ { system { linux,revision = <0x00d03114>; }; }; }; }; ''; } { name = "enable-xhci"; dtsText = '' /dts-v1/; /plugin/; / { compatible = "brcm,bcm2711"; fragment@0 { //target-path = "/scb/xhci@7e9c0000"; target = <&xhci>; __overlay__ { status = "okay"; }; }; }; ''; } ] else [ { name = "bcm2712d0-overlay"; dtsFile = "${pkgs.${namespace}.raspberrypi-overlays}/share/raspberrypi-overlays/bcm2712d0-overlay.dts"; } ]); }; firmware = [ pkgs.${namespace}.raspberryPiWirelessFirmware ]; graphics.enable32Bit = lib.mkForce false; i2c.enable = lib.mkDefault true; }; system = { build.installBootLoader = lib.mkOverride 60 (builder."uboot"); boot = { loader = { id = lib.mkOverride 60 ("raspberrypi-uboot"); kernelFile = pkgs.stdenv.hostPlatform.linux-kernel.target; }; }; # Pi specific system tags nixos.tags = [ "raspberry-pi-${cfg.variant}" # config.boot.loader.raspberry-pi.bootloader config.boot.kernelPackages.kernel.version ]; }; # Common programs programs.kdeconnect.enable = lib.mkDefault false; # Root user shell configuration users = { users.root.shell = pkgs.zsh; extraGroups = { gpio = {}; i2c = {}; input = {}; plugdev = {}; spi = {}; video = {}; }; }; services = { udev.packages = [ pkgs.${namespace}.udev-rules ]; xserver.extraConfig = let identifier = "rp1"; driver = "rp1-vec|rp1-dsi|rp1-dpi"; in '' Section "OutputClass" Identifier "${identifier}" MatchDriver "${driver}" Driver "modesetting" Option "PrimaryGPU" "true" EndSection ''; }; nixpkgs.overlays = [ ] ++ (if cfg.variant == "5" then [ (final: prev: { # https://github.com/nvmd/nixos-raspberrypi/issues/64 # credit for the initial version of this snippet goes to @micahcc jemalloc = prev.jemalloc.overrideAttrs (old: { # --with-lg-page=(log2 page_size) # RPi5 (bcm2712): since our page size is 16384 (2**14), we need 14 configureFlags = let pageSizeFlag = "--with-lg-page"; in (prev.lib.filter (flag: prev.lib.hasPrefix pageSizeFlag flag == false) old.configureFlags) ++ [ "${pageSizeFlag}=14" ]; }); }) ] else [ ]) ++ (if cfg.apply-overlays-dtmerge.enable then [ dt_ao_overlay ] else [ ]); }; }