me like nix
1{ ... }: {
2 flake.modules.nixos.pi-camera =
3 { pkgs, lib, config, ... }:
4 let
5 cfg = config.pi;
6
7 deviceTree_overlay = _final: prev: {
8 deviceTree = {
9 applyOverlays = prev.callPackage ../hosts/pi-common/overlays/apply-overlays-dtmerge.nix { };
10 compileDTS = prev.deviceTree.compileDTS;
11 };
12 };
13
14 libcamera-rpi = pkgs.libcamera.overrideAttrs (old: {
15 mesonFlags = (old.mesonFlags or [ ]) ++ [
16 "-Dipas=rpi/vc4,rpi/pisp"
17 "-Dpipelines=rpi/vc4,rpi/pisp"
18 ];
19 });
20
21 rpicam-apps = pkgs.stdenv.mkDerivation rec {
22 pname = "rpicam-apps";
23 version = "1.11.1";
24
25 src = pkgs.fetchFromGitHub {
26 owner = "raspberrypi";
27 repo = "rpicam-apps";
28 rev = "v${version}";
29 hash = "sha256-hVoKbvWFeramPkHuibJwUgFOPS9v588+K8828a1fNnA=";
30 };
31
32 nativeBuildInputs = with pkgs; [
33 meson
34 ninja
35 pkg-config
36 ];
37
38 buildInputs = [
39 libcamera-rpi
40 pkgs.libdrm
41 pkgs.libexif
42 pkgs.libjpeg
43 pkgs.libpng
44 pkgs.libtiff
45 pkgs.boost
46 pkgs.ffmpeg
47 ];
48
49 mesonFlags = [
50 "-Denable_libav=enabled"
51 "-Denable_drm=enabled"
52 "-Denable_egl=disabled"
53 "-Denable_qt=disabled"
54 "-Denable_opencv=disabled"
55 "-Denable_tflite=disabled"
56 "-Denable_hailo=disabled"
57 ];
58
59 meta = with lib; {
60 description = "Raspberry Pi camera applications";
61 homepage = "https://github.com/raspberrypi/rpicam-apps";
62 license = licenses.bsd2;
63 platforms = [ "aarch64-linux" ];
64 };
65 };
66 in
67 {
68 options.pi = {
69 streamName = lib.mkOption {
70 type = lib.types.str;
71 description = "Name of the camera stream";
72 };
73 resolution = {
74 width = lib.mkOption {
75 type = lib.types.int;
76 default = 1920;
77 description = "Camera resolution width";
78 };
79 height = lib.mkOption {
80 type = lib.types.int;
81 default = 1080;
82 description = "Camera resolution height";
83 };
84 };
85 framerate = lib.mkOption {
86 type = lib.types.int;
87 default = 30;
88 description = "Camera framerate";
89 };
90 deviceTreeFilter = lib.mkOption {
91 type = lib.types.str;
92 description = "Device tree filter pattern";
93 };
94 deviceTreeCompatible = lib.mkOption {
95 type = lib.types.str;
96 description = "Device tree compatible string";
97 };
98 gpuMem = lib.mkOption {
99 type = lib.types.int;
100 default = 256;
101 description = "GPU memory allocation in MB";
102 };
103 flipCamera = lib.mkOption {
104 type = lib.types.bool;
105 default = false;
106 description = "Flip camera image 180 degrees";
107 };
108 };
109
110 config = {
111 # CMA is configured via device tree overlay (cma-256m)
112 # Do NOT use boot.kernelParams cma= as it bypasses the DT node
113 # and prevents /dev/dma_heap/linux,cma from being created
114
115 nix.settings.trusted-users = [ "sean" ];
116
117 networking.useDHCP = true;
118 nixpkgs.overlays = [ deviceTree_overlay ];
119
120 boot.supportedFilesystems = lib.mkForce [
121 "vfat"
122 "ext4"
123 ];
124 boot.initrd.systemd.enable = false;
125
126 services.openssh = {
127 enable = true;
128 settings = {
129 PasswordAuthentication = false;
130 PermitRootLogin = "no";
131 };
132 };
133
134 users.users.sean = {
135 isNormalUser = true;
136 extraGroups = [
137 "wheel"
138 "video"
139 ];
140 openssh.authorizedKeys.keys = [
141 "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc="
142 "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16"
143 ];
144 };
145
146 security.sudo.wheelNeedsPassword = false;
147
148 services.go2rtc = {
149 enable = true;
150 settings = {
151 ffmpeg.bin = "${pkgs.ffmpeg}/bin/ffmpeg";
152 streams = {
153 "${cfg.streamName}" = "exec:${rpicam-apps}/bin/rpicam-vid -t 0 --width ${toString cfg.resolution.width} --height ${toString cfg.resolution.height} --framerate ${toString cfg.framerate} --codec h264 --inline --autofocus-mode continuous${lib.optionalString cfg.flipCamera " --vflip --hflip"} -o -";
154 };
155 };
156 };
157
158 services.udev.extraRules = ''
159 SUBSYSTEM=="dma_heap", GROUP="video", MODE="0660"
160 '';
161
162 systemd.services.go2rtc.serviceConfig = {
163 User = lib.mkForce "root";
164 SupplementaryGroups = [ "video" ];
165 };
166 systemd.services.go2rtc.environment = {
167 LIBCAMERA_IPA_MODULE_PATH = "${libcamera-rpi}/lib/libcamera/ipa";
168 };
169
170 environment.systemPackages = [
171 pkgs.ffmpeg
172 pkgs.libraspberrypi
173 libcamera-rpi
174 rpicam-apps
175 pkgs.v4l-utils
176 ];
177
178 hardware.deviceTree.filter = cfg.deviceTreeFilter;
179 hardware.deviceTree.overlays = [
180 {
181 # Increase CMA from 64MB to 256MB via device tree
182 # (cmdline cma= bypasses the DT node and /dev/dma_heap/linux,cma isn't created)
183 name = "cma-256m";
184 dtsText = ''
185 /dts-v1/;
186 /plugin/;
187
188 / {
189 compatible = "${cfg.deviceTreeCompatible}";
190
191 fragment@99 {
192 target-path = "/reserved-memory/linux,cma";
193 __overlay__ {
194 size = <0x00 0x10000000>; /* 256MB */
195 };
196 };
197 };
198 '';
199 }
200 {
201 name = "imx708-overlay";
202 dtsText = ''
203 // SPDX-License-Identifier: GPL-2.0-only
204 // Definitions for IMX708 camera module on VC I2C bus
205 /dts-v1/;
206 /plugin/;
207
208 /{
209 compatible = "${cfg.deviceTreeCompatible}";
210
211 fragment@0 {
212 target = <&i2c0if>;
213 __overlay__ {
214 status = "okay";
215 };
216 };
217
218 clk_frag: fragment@1 {
219 target = <&cam1_clk>;
220 __overlay__ {
221 status = "okay";
222 clock-frequency = <24000000>;
223 };
224 };
225
226 fragment@2 {
227 target = <&i2c0mux>;
228 __overlay__ {
229 status = "okay";
230 };
231 };
232
233 reg_frag: fragment@3 {
234 target = <&cam1_reg>;
235 cam_reg: __overlay__ {
236 startup-delay-us = <70000>;
237 off-on-delay-us = <30000>;
238 regulator-min-microvolt = <2700000>;
239 regulator-max-microvolt = <2700000>;
240 };
241 };
242
243 i2c_frag: fragment@100 {
244 target = <&i2c_csi_dsi>;
245 __overlay__ {
246 #address-cells = <1>;
247 #size-cells = <0>;
248 status = "okay";
249
250 cam_node: imx708@1a {
251 compatible = "sony,imx708";
252 reg = <0x1a>;
253 status = "okay";
254
255 clocks = <&cam1_clk>;
256 clock-names = "inclk";
257
258 vana1-supply = <&cam1_reg>;
259 vana2-supply = <&cam_dummy_reg>;
260 vdig-supply = <&cam_dummy_reg>;
261 vddl-supply = <&cam_dummy_reg>;
262
263 rotation = <180>;
264 orientation = <2>;
265
266 port {
267 cam_endpoint: endpoint {
268 clock-lanes = <0>;
269 data-lanes = <1 2>;
270 clock-noncontinuous;
271 link-frequencies =
272 /bits/ 64 <450000000>;
273 };
274 };
275 };
276
277 vcm_node: dw9817@c {
278 compatible = "dongwoon,dw9817-vcm";
279 reg = <0x0c>;
280 status = "okay";
281 VDD-supply = <&cam1_reg>;
282 };
283 };
284 };
285
286 csi_frag: fragment@101 {
287 target = <&csi1>;
288 csi: __overlay__ {
289 status = "okay";
290 brcm,media-controller;
291
292 port {
293 csi_ep: endpoint {
294 remote-endpoint = <&cam_endpoint>;
295 clock-lanes = <0>;
296 data-lanes = <1 2>;
297 clock-noncontinuous;
298 };
299 };
300 };
301 };
302
303 __overrides__ {
304 rotation = <&cam_node>,"rotation:0";
305 orientation = <&cam_node>,"orientation:0";
306 media-controller = <&csi>,"brcm,media-controller?";
307 cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
308 <&csi_frag>, "target:0=",<&csi0>,
309 <&clk_frag>, "target:0=",<&cam0_clk>,
310 <®_frag>, "target:0=",<&cam0_reg>,
311 <&cam_node>, "clocks:0=",<&cam0_clk>,
312 <&cam_node>, "vana1-supply:0=",<&cam0_reg>,
313 <&vcm_node>, "VDD-supply:0=",<&cam0_reg>;
314 vcm = <&vcm_node>, "status",
315 <0>, "=4";
316 link-frequency = <&cam_endpoint>,"link-frequencies#0";
317 };
318 };
319
320 &cam_endpoint {
321 remote-endpoint = <&csi_ep>;
322 };
323 '';
324 }
325 ];
326
327 hardware.enableRedistributableFirmware = true;
328
329 sdImage.populateFirmwareCommands = lib.mkAfter ''
330 chmod u+w ./firmware/config.txt
331 cat >> ./firmware/config.txt << EOF
332
333 # Camera support - Pi Camera v3 (IMX708)
334 gpu_mem=${toString cfg.gpuMem}
335 EOF
336
337 if [ -d ${pkgs.raspberrypifw}/share/raspberrypi/boot/overlays ]; then
338 cp -r ${pkgs.raspberrypifw}/share/raspberrypi/boot/overlays ./firmware/
339 fi
340 '';
341
342 networking.firewall.allowedTCPPorts = [
343 22
344 1984
345 8554
346 ];
347 };
348 };
349}