diff options
| author | Mel <mel@rnrd.eu> | 2026-07-13 20:00:00 +0200 |
|---|---|---|
| committer | Mel <mel@rnrd.eu> | 2026-07-13 20:00:00 +0200 |
| commit | dd8f085fc0232b77f3db885b0670a3e8d3094d36 (patch) | |
| tree | 940d89b684dbaa5175d816ad3d1bdafc8a397e1c | |
| parent | 398608948fa730883d234a636cb33f73f2da51cf (diff) | |
| download | mhs35-drm-dd8f085fc0232b77f3db885b0670a3e8d3094d36.tar.zst mhs35-drm-dd8f085fc0232b77f3db885b0670a3e8d3094d36.zip | |
Signed-off-by: Mel <mel@rnrd.eu>
| -rw-r--r-- | flake.nix | 20 | ||||
| -rw-r--r-- | module.nix | 117 | ||||
| -rw-r--r-- | package.nix | 41 |
3 files changed, 178 insertions, 0 deletions
diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..2f3d5d3 --- /dev/null +++ b/flake.nix @@ -0,0 +1,20 @@ +{ + description = "DRM driver and NixOS module for goodtft MHS-3.5 Raspberry Pi displays"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + + outputs = { self, nixpkgs }: + let + system = "aarch64-linux"; + pkgs = import nixpkgs { inherit system; }; + in + { + nixosModules.default = import ./module.nix; + + # standalone kernel module. + # built against the default nixpkgs kernel, if you need it inside + # of your kernel, use the nixos module above! + packages.${system}.default = + pkgs.linuxPackages.callPackage ./package.nix { }; + }; +} diff --git a/module.nix b/module.nix new file mode 100644 index 0000000..2cb054c --- /dev/null +++ b/module.nix @@ -0,0 +1,117 @@ +# nixos module to fully configure mhs35 support both in kernel and userland. +{ + config, + lib, + ... +}: + +let + inherit (lib) + mkEnableOption + mkIf + mkOption + optional + types + ; + + cfg = config.hardware.mhs35; + + mhs35-module = config.boot.kernelPackages.callPackage ./package.nix { }; + + # just replace the text to avoid messing with merging device trees, + # it's just simpler this way... :) + panelDts = builtins.replaceStrings + [ "rotation = <180>" "spi-max-frequency = <8000000>" ] + [ + "rotation = <${toString cfg.rotation}>" + "spi-max-frequency = <${toString cfg.spiFrequencyHz}>" + ] + (builtins.readFile ./dts/mhs35-overlay.dts); + + # touch matrices for panel. (libinput) + # TODO(mel): 90 & 270 are missing, but are of course supported. + # let's write the matrix out for them, too! + defaultMatrices = { + "0" = "0 1 0 -1 0 1"; + "180" = "0 -1 1 1 0 0"; + }; +in +{ + options.hardware.mhs35 = { + enable = mkEnableOption "goodtft MHS-3.5 SPI display support"; + + rotation = mkOption { + type = types.enum [ 0 90 180 270 ]; + default = 180; + description = '' + The panel rotation. + ''; + # orientations (for pi4, when looking at screen upright): + # * 0 - landscape, usb/rj-45 ports on the left + # * 90 - portrait, ports on top + # * 180 - landscape, ports on the right + # * 270 - portrait, ports on bottom + }; + + spiFrequencyHz = mkOption { + type = types.ints.positive; + default = 8000000; + description = '' + Panel SPI frequency. + 8 MHz is the very very conservative default, the panel + claims to support upto 125MHz. + ''; + }; + + touch = { + enable = mkEnableOption "MHS-3.5 ADS7846 touch support" // { + default = true; + }; + + calibrationMatrix = mkOption { + type = types.nullOr types.str; + default = defaultMatrices.${toString cfg.rotation} or null; + description = '' + Touch calibration matrix for touchscreen (libinput). + Set as LIBINPUT_CALIBRATION_MATRIX in udev. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = !cfg.touch.enable || cfg.touch.calibrationMatrix != null; + message = '' + hardware.mhs35: no known touch calibration matrix for + rotation ${toString cfg.rotation}! + set `hardware.mhs35.touch.calibrationMatrix` explicitly. + (and consider contributing the matrix! :3) + ''; + } + ]; + + boot.extraModulePackages = [ mhs35-module ]; + + # do not let the default in-tree ili9486 win control over our device! + # (it also lists compatibility with "waveshare,rpi-lcd-35") + boot.blacklistedKernelModules = [ "ili9486" ]; + + hardware.deviceTree.overlays = [ + { + name = "mhs35-overlay"; + dtsText = panelDts; + } + ] ++ optional cfg.touch.enable { + name = "mhs35-touch-overlay"; + dtsFile = ./dts/mhs35-touch-overlay.dts; + }; + + services.libinput.enable = mkIf cfg.touch.enable true; + + services.udev.extraRules = mkIf cfg.touch.enable '' + ATTRS{name}=="ADS7846 Touchscreen", ENV{LIBINPUT_CALIBRATION_MATRIX}="${cfg.touch.calibrationMatrix}" + ''; + }; +} diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..78ffd24 --- /dev/null +++ b/package.nix @@ -0,0 +1,41 @@ +# kernel module derivation. +# use via: `config.boot.kernelPackages.callPackage ./package.nix { }` +{ + lib, + stdenv, + kernel, +}: + +stdenv.mkDerivation { + pname = "mhs35"; + version = "1.0"; + + src = lib.fileset.toSource { + root = ./.; + fileset = lib.fileset.unions [ + ./mhs35.c + ./Kbuild + ]; + }; + + hardeningDisable = [ "pic" ]; + nativeBuildInputs = kernel.moduleBuildDependencies; + + makeFlags = [ + "-C" + "${kernel.dev}/lib/modules/${kernel.modDirVersion}/build" + "M=$(PWD)" + "modules" + ]; + + installPhase = '' + install -D mhs35.ko \ + $out/lib/modules/${kernel.modDirVersion}/extra/mhs35.ko + ''; + + meta = { + description = "DRM driver for goodtft MHS-3.5 panels"; + license = lib.licenses.gpl2Plus; + platforms = [ "aarch64-linux" ]; + }; +} |
