From 26fe213fcd886e812011da79162b540b7266198f Mon Sep 17 00:00:00 2001 From: David Wagner Date: Thu, 25 Jul 2024 09:57:34 +0200 Subject: [PATCH] rp3: Raspberry Pi V1 camera support with the libcamera stack Build libcamera and rpicam-apps from the Raspberry Pi foundation and apply the OV5647 overlay. Relevant documentation: * https://forums.raspberrypi.com/viewtopic.php?t=362707 * https://www.raspberrypi.com/documentation/computers/camera_software.html --- host-rp3.nix | 2 +- modules/camera-rpi-v1/default.nix | 161 ++++++++++++++++++ .../overlays/apply-overlays-dtmerge.nix | 50 ++++++ modules/camera-rpi-v1/overlays/libcamera.nix | 40 +++++ modules/camera-rpi-v1/rpicam-apps.nix | 71 ++++++++ modules/camera.nix | 3 - 6 files changed, 323 insertions(+), 4 deletions(-) create mode 100644 modules/camera-rpi-v1/default.nix create mode 100644 modules/camera-rpi-v1/overlays/apply-overlays-dtmerge.nix create mode 100644 modules/camera-rpi-v1/overlays/libcamera.nix create mode 100644 modules/camera-rpi-v1/rpicam-apps.nix delete mode 100644 modules/camera.nix diff --git a/host-rp3.nix b/host-rp3.nix index cabc2ac..db4de01 100644 --- a/host-rp3.nix +++ b/host-rp3.nix @@ -4,7 +4,7 @@ imports = [ nixos-hardware.nixosModules.raspberry-pi-3 ./hardware/rp3.nix - ./modules/camera.nix + ./modules/camera-rpi-v1 ./modules/common.nix ./modules/cachix.nix ./modules/remote-builder diff --git a/modules/camera-rpi-v1/default.nix b/modules/camera-rpi-v1/default.nix new file mode 100644 index 0000000..0a7e52b --- /dev/null +++ b/modules/camera-rpi-v1/default.nix @@ -0,0 +1,161 @@ +# Raspberry Pi V1 camera module (OV5647) +{ config, pkgs, ... }: + +let + + # Workaround from https://github.com/NixOS/nixos-hardware/blob/master/raspberry-pi/4/apply-overlays-dtmerge.nix + deviceTree_overlay = _final: prev: { + deviceTree = { + applyOverlays = prev.callPackage ./overlays/apply-overlays-dtmerge.nix { }; + compileDTS = prev.deviceTree.compileDTS; + }; + }; + + libcamera_overlay = (import ./overlays/libcamera.nix); + + rpicam-apps_overlay = _final: prev: { + rpicam-apps = prev.callPackage ./rpicam-apps.nix { }; + }; + +in + +{ + nixpkgs.overlays = [ + deviceTree_overlay + libcamera_overlay + rpicam-apps_overlay + ]; + + environment.systemPackages = with pkgs; [ + rpicam-apps + ]; + + hardware.deviceTree.filter = "bcm2837-rpi-3*"; + hardware.deviceTree.overlays = [ + # Equivalent to: + # https://github.com/raspberrypi/linux/blob/rpi-6.1.y/arch/arm/boot/dts/overlays/ov5647-overlay.dts + # + # As proposed in https://github.com/NixOS/nixpkgs/issues/125354, the "compatible" attribute is changed to bcm2837. + { + name = "ov5647-overlay"; + dtsText = '' + // SPDX-License-Identifier: GPL-2.0-only + // Definitions for OV5647 camera module on VC I2C bus + /dts-v1/; + /plugin/; + + /{ + compatible = "brcm,bcm2837"; + + i2c_frag: fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + cam_node: ov5647@36 { + compatible = "ovti,ov5647"; + reg = <0x36>; + status = "disabled"; + + clocks = <&cam1_clk>; + + avdd-supply = <&cam1_reg>; + dovdd-supply = <&cam_dummy_reg>; + dvdd-supply = <&cam_dummy_reg>; + + rotation = <0>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <297000000>; + }; + }; + }; + + vcm_node: ad5398@c { + compatible = "adi,ad5398"; + reg = <0x0c>; + status = "disabled"; + VANA-supply = <&cam1_reg>; + }; + }; + }; + + csi_frag: fragment@1 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + brcm,media-controller; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + data-lanes = <1 2>; + }; + }; + }; + }; + + fragment@2 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@3 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + reg_frag: fragment@4 { + target = <&cam1_reg>; + __overlay__ { + startup-delay-us = <20000>; + }; + }; + + clk_frag: fragment@5 { + target = <&cam1_clk>; + __overlay__ { + status = "okay"; + clock-frequency = <25000000>; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <&csi>,"brcm,media-controller?"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, + <&csi_frag>, "target:0=",<&csi0>, + <®_frag>, "target:0=",<&cam0_reg>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "avdd-supply:0=",<&cam0_reg>, + <&vcm_node>, "VANA-supply:0=",<&cam0_reg>; + vcm = <&vcm_node>, "status=okay", + <&cam_node>,"lens-focus:0=", <&vcm_node>; + }; + }; + + &cam_node { + status = "okay"; + }; + + &cam_endpoint { + remote-endpoint = <&csi_ep>; + }; + ''; + } + ]; +} diff --git a/modules/camera-rpi-v1/overlays/apply-overlays-dtmerge.nix b/modules/camera-rpi-v1/overlays/apply-overlays-dtmerge.nix new file mode 100644 index 0000000..98ef3fa --- /dev/null +++ b/modules/camera-rpi-v1/overlays/apply-overlays-dtmerge.nix @@ -0,0 +1,50 @@ +# modification of nixpkgs deviceTree.applyOverlays to resolve https://github.com/NixOS/nixpkgs/issues/125354 +# derived from https://github.com/NixOS/nixpkgs/blob/916ca8f2b0c208def051f8ea9760c534a40309db/pkgs/os-specific/linux/device-tree/default.nix +{ lib, stdenvNoCC, dtc, libraspberrypi }: + +with lib; (base: overlays': stdenvNoCC.mkDerivation { + name = "device-tree-overlays"; + nativeBuildInputs = [ dtc libraspberrypi ]; + buildCommand = + let + overlays = toList overlays'; + in + '' + mkdir -p $out + cd "${base}" + find . -type f -name '*.dtb' -print0 \ + | xargs -0 cp -v --no-preserve=mode --target-directory "$out" --parents + + for dtb in $(find "$out" -type f -name '*.dtb'); do + dtbCompat=$(fdtget -t s "$dtb" / compatible 2>/dev/null || true) + # skip files without `compatible` string + test -z "$dtbCompat" && continue + + ${flip (concatMapStringsSep "\n") overlays (o: '' + overlayCompat="$(fdtget -t s "${o.dtboFile}" / compatible)" + + # skip incompatible and non-matching overlays + if [[ ! "$dtbCompat" =~ "$overlayCompat" ]]; then + echo "Skipping overlay ${o.name}: incompatible with $(basename "$dtb")" + elif ${if ((o.filter or null) == null) then "false" else '' + [[ "''${dtb//${o.filter}/}" == "$dtb" ]] + ''} + then + echo "Skipping overlay ${o.name}: filter does not match $(basename "$dtb")" + else + echo -n "Applying overlay ${o.name} to $(basename "$dtb")... " + mv "$dtb"{,.in} + + # dtmerge requires a .dtbo ext for dtbo files, otherwise it adds it to the given file implicitly + dtboWithExt="$TMPDIR/$(basename "${o.dtboFile}").dtbo" + cp -r ${o.dtboFile} "$dtboWithExt" + + dtmerge "$dtb.in" "$dtb" "$dtboWithExt" + + echo "ok" + rm "$dtb.in" "$dtboWithExt" + fi + '')} + + done''; +}) diff --git a/modules/camera-rpi-v1/overlays/libcamera.nix b/modules/camera-rpi-v1/overlays/libcamera.nix new file mode 100644 index 0000000..585914a --- /dev/null +++ b/modules/camera-rpi-v1/overlays/libcamera.nix @@ -0,0 +1,40 @@ +final: prev: { + libcamera = prev.libcamera.overrideAttrs (old: { + buildInputs = old.buildInputs ++ [ prev.boost prev.nlohmann_json ]; + nativeBuildInputs = old.nativeBuildInputs ++ [ prev.python3Packages.pybind11 ]; + + BOOST_INCLUDEDIR = "${prev.lib.getDev prev.boost}/include"; + BOOST_LIBRARYDIR = "${prev.lib.getLib prev.boost}/lib"; + + postPatch = old.postPatch + '' + patchShebangs src/py/libcamera + ''; + + mesonFlags = old.mesonFlags ++ [ + "-Dcam=disabled" + "-Dgstreamer=disabled" + "-Dipas=rpi/vc4,rpi/pisp" + "-Dpipelines=rpi/vc4,rpi/pisp" + ]; + + src = prev.fetchFromGitHub { + owner = "raspberrypi"; + repo = "libcamera"; + rev = "6ddd79b5bdbedc1f61007aed35391f1559f9e29a"; + sha256 = "eFIiYCsuukPuG6iqHZeKsXQYSuZ+9q5oLNwuJJ+bAhk="; + + nativeBuildInputs = [ prev.git ]; + + postFetch = '' + cd "$out" + + export NIX_SSL_CERT_FILE=${prev.cacert}/etc/ssl/certs/ca-bundle.crt + + ${prev.lib.getExe prev.meson} subprojects download \ + libpisp + + find subprojects -type d -name .git -prune -execdir rm -r {} + + ''; + }; + }); +} diff --git a/modules/camera-rpi-v1/rpicam-apps.nix b/modules/camera-rpi-v1/rpicam-apps.nix new file mode 100644 index 0000000..d788eb7 --- /dev/null +++ b/modules/camera-rpi-v1/rpicam-apps.nix @@ -0,0 +1,71 @@ +# Based on https://github.com/NixOS/nixpkgs/pull/281803 +{ stdenv +, fetchFromGitHub +, lib +, makeWrapper +, meson +, ninja +, pkg-config +, boost +, ffmpeg-headless +, libcamera +, libdrm +, libepoxy +, libexif +, libjpeg +, libpng +, libtiff +, libX11 +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "rpicam-apps"; + version = "1.5.0"; + + src = fetchFromGitHub { + owner = "raspberrypi"; + repo = "rpicam-apps"; + rev = "v${finalAttrs.version}"; + hash = "sha256-s4zJh6r3VhiquO54KWZ78dVCH1BmlphY9zEB9BidNyo="; + }; + + buildInputs = [ + boost + ffmpeg-headless + libcamera + libdrm + libepoxy # GLES/EGL preview window + libexif + libjpeg + libpng + libtiff + libX11 + ]; + + nativeBuildInputs = [ + makeWrapper + meson + ninja + pkg-config + ]; + + # Meson is no longer able to pick up Boost automatically. + # https://github.com/NixOS/nixpkgs/issues/86131 + BOOST_INCLUDEDIR = "${lib.getDev boost}/include"; + BOOST_LIBRARYDIR = "${lib.getLib boost}/lib"; + + # See all options here: https://github.com/raspberrypi/rpicam-apps/blob/main/meson_options.txt + mesonFlags = [ + "-Denable_drm=disabled" + "-Denable_egl=disabled" + "-Denable_hailo=disabled" + "-Denable_qt=disabled" + ]; + + postInstall = '' + for f in rpicam-hello rpicam-jpeg rpicam-raw rpicam-still rpicam-vid + do + wrapProgram $out/bin/$f --set-default LIBCAMERA_IPA_PROXY_PATH ${libcamera}/libexec/libcamera + done + ''; +}) diff --git a/modules/camera.nix b/modules/camera.nix deleted file mode 100644 index c8a8176..0000000 --- a/modules/camera.nix +++ /dev/null @@ -1,3 +0,0 @@ -{ config, ... }: - -{ }