1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.hardware.display;
9in
10{
11 meta.doc = ./display.md;
12 meta.maintainers = with lib.maintainers; [
13 nazarewk
14 ];
15
16 options = {
17 hardware.display.edid.enable = lib.mkOption {
18 type = with lib.types; bool;
19 default = cfg.edid.packages != null;
20 defaultText = lib.literalExpression "config.hardware.display.edid.packages != null";
21 description = ''
22 Enables handling of EDID files
23 '';
24 };
25
26 hardware.display.edid.packages = lib.mkOption {
27 type = with lib.types; listOf package;
28 default = [ ];
29 description = ''
30 List of packages containing EDID binary files at `$out/lib/firmware/edid`.
31 Such files will be available for use in `drm.edid_firmware` kernel
32 parameter as `edid/<filename>`.
33
34 You can craft one directly here or use sibling options `linuxhw` and `modelines`.
35 '';
36 example = lib.literalExpression ''
37 [
38 (pkgs.runCommand "edid-custom" {} '''
39 mkdir -p "$out/lib/firmware/edid"
40 base64 -d > "$out/lib/firmware/edid/custom1.bin" <<'EOF'
41 <insert your base64 encoded EDID file here `base64 < /sys/class/drm/card0-.../edid`>
42 EOF
43 ''')
44 ]
45 '';
46 apply =
47 list:
48 if list == [ ] then
49 null
50 else
51 (pkgs.buildEnv {
52 name = "firmware-edid";
53 paths = list;
54 pathsToLink = [ "/lib/firmware/edid" ];
55 ignoreCollisions = true;
56 })
57 // {
58 compressFirmware = false;
59 };
60 };
61
62 hardware.display.edid.linuxhw = lib.mkOption {
63 type = with lib.types; attrsOf (listOf str);
64 default = { };
65 description = ''
66 Exposes EDID files from users-sourced database at <https://github.com/linuxhw/EDID>
67
68 Attribute names will be mapped to EDID filenames `<NAME>.bin`.
69
70 Attribute values are lists of `awk` regexp patterns that (together) must match
71 exactly one line in either of:
72 - [AnalogDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/AnalogDisplay.md)
73 - [DigitalDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/DigitalDisplay.md)
74
75 There is no universal way of locating your device config, but here are some practical tips:
76 1. locate your device:
77 - find your model number (second column)
78 - locate manufacturer (first column) and go through the list manually
79 2. narrow down results using other columns until there is only one left:
80 - `Name` column
81 - production date (`Made` column)
82 - resolution `Res`
83 - screen diagonal (`Inch` column)
84 - as a last resort use `ID` from the last column
85 '';
86 example = lib.literalExpression ''
87 {
88 PG278Q_2014 = [ "PG278Q" "2014" ];
89 }
90 '';
91 apply =
92 displays:
93 if displays == { } then null else pkgs.linuxhw-edid-fetcher.override { inherit displays; };
94 };
95
96 hardware.display.edid.modelines = lib.mkOption {
97 type = with lib.types; attrsOf str;
98 default = { };
99 description = ''
100 Attribute set of XFree86 Modelines automatically converted
101 and exposed as `edid/<name>.bin` files in initrd.
102 See for more information:
103 - <https://en.wikipedia.org/wiki/XFree86_Modeline>
104 '';
105 example = lib.literalExpression ''
106 {
107 "PG278Q_60" = " 241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync";
108 "PG278Q_120" = " 497.75 2560 2608 2640 2720 1440 1443 1448 1525 +hsync -vsync";
109 "U2711_60" = " 241.50 2560 2600 2632 2720 1440 1443 1448 1481 -hsync +vsync";
110 }
111 '';
112 apply =
113 modelines:
114 if modelines == { } then
115 null
116 else
117 pkgs.edid-generator.overrideAttrs {
118 clean = true;
119 passthru.config = modelines;
120 modelines = lib.trivial.pipe modelines [
121 (lib.mapAttrsToList (
122 name: value:
123 lib.throwIfNot (
124 builtins.stringLength name <= 12
125 ) "Modeline name must be 12 characters or less" ''Modeline "${name}" ${value}''
126 ))
127 (builtins.map (line: "${line}\n"))
128 (lib.strings.concatStringsSep "")
129 ];
130 };
131 };
132
133 hardware.display.outputs = lib.mkOption {
134 type = lib.types.attrsOf (
135 lib.types.submodule ({
136 options = {
137 edid = lib.mkOption {
138 type = with lib.types; nullOr str;
139 default = null;
140 description = ''
141 An EDID filename to be used for configured display, as in `edid/<filename>`.
142 See for more information:
143 - `hardware.display.edid.packages`
144 - https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes_and_EDID
145 '';
146 };
147 mode = lib.mkOption {
148 type = with lib.types; nullOr str;
149 default = null;
150 description = ''
151 A `video` kernel parameter (framebuffer mode) configuration for the specific output:
152
153 <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m][eDd]
154
155 See for more information:
156 - https://docs.kernel.org/fb/modedb.html
157 - https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes
158 '';
159 example = lib.literalExpression ''
160 "e"
161 '';
162 };
163 };
164 })
165 );
166 description = ''
167 Hardware/kernel-level configuration of specific outputs.
168 '';
169 default = { };
170
171 example = lib.literalExpression ''
172 {
173 edid.modelines."PG278Q_60" = "241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync";
174 outputs."DP-1".edid = "PG278Q_60.bin";
175 outputs."DP-1".mode = "e";
176 }
177 '';
178 };
179 };
180
181 config = lib.mkMerge [
182 {
183 hardware.display.edid.packages =
184 lib.optional (cfg.edid.modelines != null) cfg.edid.modelines
185 ++ lib.optional (cfg.edid.linuxhw != null) cfg.edid.linuxhw;
186
187 boot.kernelParams =
188 # forcing video modes
189 lib.trivial.pipe cfg.outputs [
190 (lib.attrsets.filterAttrs (_: spec: spec.mode != null))
191 (lib.mapAttrsToList (output: spec: "video=${output}:${spec.mode}"))
192 ]
193 # selecting EDID for displays
194 ++ lib.trivial.pipe cfg.outputs [
195 (lib.attrsets.filterAttrs (_: spec: spec.edid != null))
196 (lib.mapAttrsToList (output: spec: "${output}:edid/${spec.edid}"))
197 (builtins.concatStringsSep ",")
198 (p: lib.optional (p != "") "drm.edid_firmware=${p}")
199 ];
200 }
201 (lib.mkIf (cfg.edid.packages != null) {
202 # services.udev implements hardware.firmware option
203 services.udev.enable = true;
204 hardware.firmware = [ cfg.edid.packages ];
205 })
206 ];
207}