diff --git a/flake.lock b/flake.lock
index 57ec184..650b4f2 100644
--- a/flake.lock
+++ b/flake.lock
@@ -37,24 +37,6 @@
         "type": "github"
       }
     },
-    "naersk": {
-      "inputs": {
-        "nixpkgs": "nixpkgs_2"
-      },
-      "locked": {
-        "lastModified": 1721727458,
-        "narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=",
-        "owner": "nix-community",
-        "repo": "naersk",
-        "rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-community",
-        "repo": "naersk",
-        "type": "github"
-      }
-    },
     "nixpkgs": {
       "locked": {
         "lastModified": 1729880355,
@@ -72,19 +54,6 @@
       }
     },
     "nixpkgs_2": {
-      "locked": {
-        "lastModified": 1728888510,
-        "narHash": "sha256-nsNdSldaAyu6PE3YUA+YQLqUDJh+gRbBooMMekZJwvI=",
-        "path": "/nix/store/xnjw9gmfmpppdj6bxpw6cfkspc3h6xwl-source",
-        "rev": "a3c0b3b21515f74fd2665903d4ce6bc4dc81c77c",
-        "type": "path"
-      },
-      "original": {
-        "id": "nixpkgs",
-        "type": "indirect"
-      }
-    },
-    "nixpkgs_3": {
       "locked": {
         "lastModified": 1730137625,
         "narHash": "sha256-9z8oOgFZiaguj+bbi3k4QhAD6JabWrnv7fscC/mt0KE=",
@@ -104,9 +73,7 @@
       "inputs": {
         "fenix": "fenix",
         "flake-utils": "flake-utils",
-        "naersk": "naersk",
-        "nixpkgs": "nixpkgs_3",
-        "wiringop": "wiringop"
+        "nixpkgs": "nixpkgs_2"
       }
     },
     "rust-analyzer-src": {
@@ -140,22 +107,6 @@
         "repo": "default",
         "type": "github"
       }
-    },
-    "wiringop": {
-      "flake": false,
-      "locked": {
-        "lastModified": 1728531008,
-        "narHash": "sha256-jVfNb0YqaYFd2r5zupy8C1Q1jDAfetPRVKbhtyMBLtg=",
-        "owner": "orangepi-xunlong",
-        "repo": "wiringOP",
-        "rev": "d7bb9d97db265476e6afa8186f2406642e6946be",
-        "type": "github"
-      },
-      "original": {
-        "owner": "orangepi-xunlong",
-        "repo": "wiringOP",
-        "type": "github"
-      }
     }
   },
   "root": "root",
diff --git a/flake.nix b/flake.nix
index 0de33b2..79f00e8 100644
--- a/flake.nix
+++ b/flake.nix
@@ -6,64 +6,82 @@
 
     fenix.url = "github:nix-community/fenix";
 
-    naersk.url = "github:nix-community/naersk";
-
-    wiringop = {
-      url = "github:orangepi-xunlong/wiringOP";
-      flake = false;
-    };
-
     flake-utils.url = "github:numtide/flake-utils";
   };
 
-  outputs = { self, nixpkgs, fenix, naersk, wiringop, flake-utils }: flake-utils.lib.eachDefaultSystem (system:
+  outputs = { self, nixpkgs, fenix, flake-utils }:
     let
-      target = "aarch64-unknown-linux-gnu";
-      pkgs = import nixpkgs {
-        inherit system;
-        overlays = [
-          fenix.overlays.default
-        ];
+      mkWiringOp = { fetchFromGitHub, lib }: fetchFromGitHub {
+        owner = "orangepi-xunlong";
+        repo = "wiringOP";
+        rev = "d7bb9d97db265476e6afa8186f2406642e6946be";
+        hash = "sha256-jVfNb0YqaYFd2r5zupy8C1Q1jDAfetPRVKbhtyMBLtg=";
+      };
+    in
+    flake-utils.lib.eachDefaultSystem
+      (system:
+        let
+          target = "aarch64-unknown-linux-gnu";
+          pkgs = import nixpkgs {
+            inherit system;
+            overlays = [
+              fenix.overlays.default
+            ];
+          };
+
+          devToolchain = with fenix.packages.${system}; combine [
+            stable.cargo
+            stable.rustc
+            stable.clippy
+            stable.rustfmt
+            targets.${target}.stable.rust-std
+          ];
+
+          CARGO_BUILD_TARGET = target;
+          CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER =
+            let
+              inherit (pkgs.pkgsCross.aarch64-multiplatform.stdenv) cc;
+            in
+            "${cc}/bin/${cc.targetPrefix}cc";
+
+          wiringop-headers = pkgs.runCommandLocal "wiringOP-headers" { } ''
+            mkdir -p $out/include
+            cp ${wiringop}/wiringPi/*.h $out/include
+          '';
+
+          wiringop = pkgs.callPackage mkWiringOp { };
+
+          LIBCLANG_PATH = "${pkgs.llvmPackages_18.clang-unwrapped.lib}/lib";
+        in
+        {
+          devShells.default =
+            pkgs.mkShell {
+              nativeBuildInputs = [
+                devToolchain
+                pkgs.llvmPackages_18.clang
+              ];
+
+              buildInputs = [
+                wiringop-headers
+                pkgs.libxcrypt
+              ];
+
+              WIRINGOP_PATH = "${wiringop}";
+
+              inherit LIBCLANG_PATH CARGO_BUILD_TARGET CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER;
+            };
+        }) // {
+      overlays.default = final: prev: {
+        wiringop = final.callPackage (import ./package.nix) { srcAll = final.callPackage mkWiringOp { }; };
       };
 
-      devToolchain = with fenix.packages.${system}; combine [
-        stable.cargo
-        stable.rustc
-        stable.clippy
-        stable.rustfmt
-        targets.${target}.stable.rust-std
-      ];
-
-      CARGO_BUILD_TARGET = target;
-      CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER =
+      packages.aarch64-linux.default =
         let
-          inherit (pkgs.pkgsCross.aarch64-multiplatform.stdenv) cc;
+          pkgs = import nixpkgs {
+            system = "aarch64-linux";
+            overlays = [ self.overlays.default ];
+          };
         in
-        "${cc}/bin/${cc.targetPrefix}cc";
-
-      wiringop-headers = pkgs.runCommandLocal "wiringOP-headers" { } ''
-        mkdir -p $out/include
-        cp ${wiringop}/wiringPi/*.h $out/include
-      '';
-
-      LIBCLANG_PATH = "${pkgs.llvmPackages_18.clang-unwrapped.lib}/lib";
-    in
-    {
-      devShells.default =
-        pkgs.mkShell {
-          nativeBuildInputs = [
-            devToolchain
-            pkgs.llvmPackages_18.clang
-          ];
-
-          buildInputs = [
-            wiringop-headers
-            pkgs.libxcrypt
-          ];
-
-          WIRINGOP_PATH = "${wiringop}";
-
-          inherit LIBCLANG_PATH CARGO_BUILD_TARGET CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER;
-        };
-    });
+        pkgs.wiringop;
+    };
 }
diff --git a/package.nix b/package.nix
new file mode 100644
index 0000000..a0ad504
--- /dev/null
+++ b/package.nix
@@ -0,0 +1,78 @@
+{ lib
+, stdenv
+, symlinkJoin
+, srcAll
+, libxcrypt
+}:
+let
+  version = "master";
+
+  mkSubProject =
+    { subprj
+    , # The only mandatory argument
+      buildInputs ? [ ]
+    , src ? srcAll
+    , extraPreInstall ? ""
+    ,
+    }:
+    stdenv.mkDerivation (finalAttrs: {
+      pname = "wiringpi-${subprj}";
+      inherit version src;
+      sourceRoot = "${src.name}/${subprj}";
+      inherit buildInputs;
+      # Remove (meant for other OSs) lines from Makefiles
+      preInstall = ''
+        sed -i "/chown root/d" Makefile
+        sed -i "/chmod/d" Makefile
+      '' + extraPreInstall;
+      makeFlags = [
+        "DESTDIR=${placeholder "out"}"
+        "PREFIX="
+        # On NixOS we don't need to run ldconfig during build:
+        "LDCONFIG=echo"
+      ];
+    });
+
+  passthru = {
+    src = srcAll;
+    inherit mkSubProject;
+    wiringPi = mkSubProject {
+      subprj = "wiringPi";
+      buildInputs = [ libxcrypt ];
+    };
+    devLib = mkSubProject {
+      subprj = "devLib";
+      buildInputs = [ passthru.wiringPi ];
+    };
+    wiringPiD = mkSubProject {
+      subprj = "wiringPiD";
+      buildInputs = [
+        libxcrypt
+        passthru.wiringPi
+        passthru.devLib
+      ];
+    };
+    gpio = mkSubProject {
+      subprj = "gpio";
+      extraPreInstall = ''
+        mkdir -p $out/bin
+      '';
+      buildInputs = [
+        libxcrypt
+        passthru.wiringPi
+        passthru.devLib
+      ];
+    };
+  };
+in
+symlinkJoin {
+  name = "wiringop-${version}";
+  inherit passthru;
+  paths = builtins.attrValues {
+    inherit (passthru)
+      wiringPi
+      devLib
+      wiringPiD
+      gpio;
+  };
+}
diff --git a/src/gpio.rs b/src/gpio.rs
index c041caa..06a18c0 100644
--- a/src/gpio.rs
+++ b/src/gpio.rs
@@ -147,6 +147,54 @@ impl Pin {
         }
     }
 
+    pub fn pwm_write(&mut self, value: i32) -> Result<(), GpioError> {
+        ensure_library_setup!();
+
+        self.ensure_pin_mode(PinMode::PwmOutput)?;
+
+        unsafe {
+            ffi::pwmWrite(self.pin_id, value);
+        }
+
+        Ok(())
+    }
+
+    pub fn pwm_set_tone(&mut self, freq: i32) -> Result<(), GpioError> {
+        ensure_library_setup!();
+
+        self.ensure_pin_mode(PinMode::PwmOutput)?;
+
+        unsafe {
+            ffi::pwmToneWrite(self.pin_id, freq);
+        }
+
+        Ok(())
+    }
+
+    pub fn pwm_set_range(&mut self, range: u32) -> Result<(), GpioError> {
+        ensure_library_setup!();
+
+        self.ensure_pin_mode(PinMode::PwmOutput)?;
+
+        unsafe {
+            ffi::pwmSetRange(self.pin_id, range);
+        }
+
+        Ok(())
+    }
+
+    pub fn pwm_set_clock(&mut self, divisor: i32) -> Result<(), GpioError> {
+        ensure_library_setup!();
+
+        self.ensure_pin_mode(PinMode::PwmOutput)?;
+
+        unsafe {
+            ffi::pwmSetClock(self.pin_id, divisor);
+        }
+
+        Ok(())
+    }
+
     pub fn get_gpio_mode(&mut self) -> Result<PinMode, GpioError> {
         ensure_library_setup!();
 
diff --git a/src/lib.rs b/src/lib.rs
index b9f9ec0..fb59486 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,4 +12,4 @@ macro_rules! ensure_library_setup {
     };
 }
 
-static LIBRARY_SETUP_COMPLETE: Lazy<bool> = Lazy::new(|| unsafe { ffi::wiringPiSetupGpio() == 0 });
+static LIBRARY_SETUP_COMPLETE: Lazy<bool> = Lazy::new(|| unsafe { ffi::wiringPiSetup() == 0 });