summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--flake.nix20
-rw-r--r--module.nix117
-rw-r--r--package.nix41
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" ];
+  };
+}