diff --git a/src/devices/Gpio/Drivers/OrangePi5BDriver.cs b/src/devices/Gpio/Drivers/OrangePi5BDriver.cs
new file mode 100644
index 0000000000..c1ca4193ec
--- /dev/null
+++ b/src/devices/Gpio/Drivers/OrangePi5BDriver.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Iot.Device.Gpio.Drivers
+{
+ ///
+ /// A GPIO driver for the Orange Pi 5B (26-pin header).
+ ///
+ ///
+ /// SoC: Rockchip RK3588S
+ /// Pin mapping sourced from wiringOP (orangepi-xunlong/wiringOP).
+ ///
+ public class OrangePi5BDriver : Rk3588Driver
+ {
+ // Mapping from physical header pin (index) to GPIO logical number.
+ // -1 indicates power, ground, or non-GPIO pins.
+ private static readonly int[] _physicalToGpio = new int[]
+ {
+ -1, // 0 (no pin)
+ -1, -1, // 1 = 3.3V, 2 = 5V
+ MapPinNumber(1, 'B', 7), -1, // 3 = GPIO1_B7 (47), 4 = 5V
+ MapPinNumber(1, 'B', 6), -1, // 5 = GPIO1_B6 (46), 6 = GND
+ MapPinNumber(1, 'C', 6), MapPinNumber(4, 'A', 3), // 7 = GPIO1_C6 (54), 8 = GPIO4_A3 (131)
+ -1, MapPinNumber(4, 'A', 4), // 9 = GND, 10 = GPIO4_A4 (132)
+ MapPinNumber(4, 'B', 2), MapPinNumber(0, 'D', 5), // 11 = GPIO4_B2 (138), 12 = GPIO0_D5 (29)
+ MapPinNumber(4, 'B', 3), -1, // 13 = GPIO4_B3 (139), 14 = GND
+ MapPinNumber(0, 'D', 4), MapPinNumber(1, 'D', 3), // 15 = GPIO0_D4 (28), 16 = GPIO1_D3 (59)
+ -1, MapPinNumber(1, 'D', 2), // 17 = 3.3V, 18 = GPIO1_D2 (58)
+ MapPinNumber(1, 'C', 1), -1, // 19 = GPIO1_C1 (49), 20 = GND
+ MapPinNumber(1, 'C', 0), -1, // 21 = GPIO1_C0 (48), 22 = NC
+ MapPinNumber(1, 'C', 2), MapPinNumber(1, 'C', 4), // 23 = GPIO1_C2 (50), 24 = GPIO1_C4 (52)
+ -1, MapPinNumber(1, 'A', 3), // 25 = GND, 26 = GPIO1_A3 (35)
+ };
+
+ ///
+ protected override int PinCount => 26;
+
+ ///
+ /// Maps a physical header pin number (1-26) to the driver's logical GPIO number.
+ ///
+ /// Physical pin number on the 26-pin header (1-26).
+ /// Logical GPIO pin number for use with .
+ /// The pin is not a GPIO pin (power, ground, etc.).
+ public static int MapPhysicalPinNumber(int physicalPin)
+ {
+ if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length)
+ {
+ throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-26).", nameof(physicalPin));
+ }
+
+ int gpio = _physicalToGpio[physicalPin];
+ return gpio != -1
+ ? gpio
+ : throw new ArgumentException($"Physical pin {physicalPin} is not a GPIO pin (power/ground).", nameof(physicalPin));
+ }
+ }
+}
diff --git a/src/devices/Gpio/Drivers/OrangePi5Driver.cs b/src/devices/Gpio/Drivers/OrangePi5Driver.cs
new file mode 100644
index 0000000000..3070a72b7e
--- /dev/null
+++ b/src/devices/Gpio/Drivers/OrangePi5Driver.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Iot.Device.Gpio.Drivers
+{
+ ///
+ /// A GPIO driver for the Orange Pi 5 (26-pin header).
+ ///
+ ///
+ /// SoC: Rockchip RK3588S
+ /// Pin mapping sourced from wiringOP (orangepi-xunlong/wiringOP).
+ ///
+ public class OrangePi5Driver : Rk3588Driver
+ {
+ // Mapping from physical header pin (index) to GPIO logical number.
+ // -1 indicates power, ground, or non-GPIO pins.
+ private static readonly int[] _physicalToGpio = new int[]
+ {
+ -1, // 0 (no pin)
+ -1, -1, // 1 = 3.3V, 2 = 5V
+ MapPinNumber(1, 'B', 7), -1, // 3 = GPIO1_B7 (47), 4 = 5V
+ MapPinNumber(1, 'B', 6), -1, // 5 = GPIO1_B6 (46), 6 = GND
+ MapPinNumber(1, 'C', 6), MapPinNumber(4, 'A', 3), // 7 = GPIO1_C6 (54), 8 = GPIO4_A3 (131)
+ -1, MapPinNumber(4, 'A', 4), // 9 = GND, 10 = GPIO4_A4 (132)
+ MapPinNumber(4, 'B', 2), MapPinNumber(0, 'D', 5), // 11 = GPIO4_B2 (138), 12 = GPIO0_D5 (29)
+ MapPinNumber(4, 'B', 3), -1, // 13 = GPIO4_B3 (139), 14 = GND
+ MapPinNumber(0, 'D', 4), MapPinNumber(1, 'D', 3), // 15 = GPIO0_D4 (28), 16 = GPIO1_D3 (59)
+ -1, MapPinNumber(1, 'D', 2), // 17 = 3.3V, 18 = GPIO1_D2 (58)
+ MapPinNumber(1, 'C', 1), -1, // 19 = GPIO1_C1 (49), 20 = GND
+ MapPinNumber(1, 'C', 0), MapPinNumber(2, 'D', 4), // 21 = GPIO1_C0 (48), 22 = GPIO2_D4 (92)
+ MapPinNumber(1, 'C', 2), MapPinNumber(1, 'C', 4), // 23 = GPIO1_C2 (50), 24 = GPIO1_C4 (52)
+ -1, MapPinNumber(1, 'A', 3), // 25 = GND, 26 = GPIO1_A3 (35)
+ };
+
+ ///
+ protected override int PinCount => 26;
+
+ ///
+ /// Maps a physical header pin number (1-26) to the driver's logical GPIO number.
+ ///
+ /// Physical pin number on the 26-pin header (1-26).
+ /// Logical GPIO pin number for use with .
+ /// The pin is not a GPIO pin (power, ground, etc.).
+ public static int MapPhysicalPinNumber(int physicalPin)
+ {
+ if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length)
+ {
+ throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-26).", nameof(physicalPin));
+ }
+
+ int gpio = _physicalToGpio[physicalPin];
+ return gpio != -1
+ ? gpio
+ : throw new ArgumentException($"Physical pin {physicalPin} is not a GPIO pin (power/ground).", nameof(physicalPin));
+ }
+ }
+}
diff --git a/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs b/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs
new file mode 100644
index 0000000000..add9a5a922
--- /dev/null
+++ b/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Iot.Device.Gpio.Drivers
+{
+ ///
+ /// A GPIO driver for the Orange Pi 5 Max (40-pin header).
+ ///
+ ///
+ /// SoC: Rockchip RK3588
+ /// Pin mapping sourced from wiringOP (orangepi-xunlong/wiringOP).
+ ///
+ public class OrangePi5MaxDriver : Rk3588Driver
+ {
+ // Mapping from physical header pin (index) to GPIO logical number.
+ // -1 indicates power, ground, or non-GPIO pins.
+ private static readonly int[] _physicalToGpio = new int[]
+ {
+ -1, // 0 (no pin)
+ -1, -1, // 1 = 3.3V, 2 = 5V
+ MapPinNumber(0, 'C', 0), -1, // 3 = GPIO0_C0 (16), 4 = 5V
+ MapPinNumber(0, 'B', 7), -1, // 5 = GPIO0_B7 (15), 6 = GND
+ MapPinNumber(1, 'A', 7), MapPinNumber(0, 'B', 5), // 7 = GPIO1_A7 (39), 8 = GPIO0_B5 (13)
+ -1, MapPinNumber(0, 'B', 6), // 9 = GND, 10 = GPIO0_B6 (14)
+ MapPinNumber(1, 'A', 0), MapPinNumber(4, 'A', 6), // 11 = GPIO1_A0 (32), 12 = GPIO4_A6 (134)
+ MapPinNumber(1, 'A', 1), -1, // 13 = GPIO1_A1 (33), 14 = GND
+ MapPinNumber(1, 'A', 2), MapPinNumber(1, 'A', 3), // 15 = GPIO1_A2 (34), 16 = GPIO1_A3 (35)
+ -1, MapPinNumber(1, 'A', 4), // 17 = 3.3V, 18 = GPIO1_A4 (36)
+ MapPinNumber(1, 'B', 2), -1, // 19 = GPIO1_B2 (42), 20 = GND
+ MapPinNumber(1, 'B', 1), MapPinNumber(1, 'B', 0), // 21 = GPIO1_B1 (41), 22 = GPIO1_B0 (40)
+ MapPinNumber(1, 'B', 3), MapPinNumber(1, 'B', 4), // 23 = GPIO1_B3 (43), 24 = GPIO1_B4 (44)
+ -1, MapPinNumber(1, 'B', 5), // 25 = GND, 26 = GPIO1_B5 (45)
+ MapPinNumber(1, 'B', 7), MapPinNumber(1, 'B', 6), // 27 = GPIO1_B7 (47), 28 = GPIO1_B6 (46)
+ MapPinNumber(3, 'C', 1), -1, // 29 = GPIO3_C1 (113), 30 = GND
+ MapPinNumber(3, 'B', 5), MapPinNumber(1, 'D', 6), // 31 = GPIO3_B5 (109), 32 = GPIO1_D6 (62)
+ MapPinNumber(3, 'B', 6), -1, // 33 = GPIO3_B6 (110), 34 = GND
+ MapPinNumber(3, 'C', 2), MapPinNumber(1, 'D', 7), // 35 = GPIO3_C2 (114), 36 = GPIO1_D7 (63)
+ MapPinNumber(4, 'A', 7), MapPinNumber(3, 'C', 0), // 37 = GPIO4_A7 (135), 38 = GPIO3_C0 (112)
+ -1, MapPinNumber(3, 'B', 7), // 39 = GND, 40 = GPIO3_B7 (111)
+ };
+
+ ///
+ protected override int PinCount => 40;
+
+ ///
+ /// Maps a physical header pin number (1-40) to the driver's logical GPIO number.
+ ///
+ /// Physical pin number on the 40-pin header (1-40).
+ /// Logical GPIO pin number for use with .
+ /// The pin is not a GPIO pin (power, ground, etc.).
+ public static int MapPhysicalPinNumber(int physicalPin)
+ {
+ if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length)
+ {
+ throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-40).", nameof(physicalPin));
+ }
+
+ int gpio = _physicalToGpio[physicalPin];
+ return gpio != -1
+ ? gpio
+ : throw new ArgumentException($"Physical pin {physicalPin} is not a GPIO pin (power/ground).", nameof(physicalPin));
+ }
+ }
+}
diff --git a/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs b/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs
new file mode 100644
index 0000000000..6a7546ea15
--- /dev/null
+++ b/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Iot.Device.Gpio.Drivers
+{
+ ///
+ /// A GPIO driver for the Orange Pi 5 Plus (40-pin header).
+ ///
+ ///
+ /// SoC: Rockchip RK3588
+ /// Pin mapping sourced from wiringOP (orangepi-xunlong/wiringOP).
+ ///
+ public class OrangePi5PlusDriver : Rk3588Driver
+ {
+ // Mapping from physical header pin (index) to GPIO logical number.
+ // -1 indicates power, ground, or non-GPIO pins.
+ private static readonly int[] _physicalToGpio = new int[]
+ {
+ -1, // 0 (no pin)
+ -1, -1, // 1 = 3.3V, 2 = 5V
+ MapPinNumber(0, 'C', 0), -1, // 3 = GPIO0_C0 (16), 4 = 5V
+ MapPinNumber(0, 'B', 7), -1, // 5 = GPIO0_B7 (15), 6 = GND
+ MapPinNumber(1, 'D', 6), MapPinNumber(1, 'A', 1), // 7 = GPIO1_D6 (62), 8 = GPIO1_A1 (33)
+ -1, MapPinNumber(1, 'A', 0), // 9 = GND, 10 = GPIO1_A0 (32)
+ MapPinNumber(1, 'A', 4), MapPinNumber(3, 'A', 1), // 11 = GPIO1_A4 (36), 12 = GPIO3_A1 (97)
+ MapPinNumber(1, 'A', 7), -1, // 13 = GPIO1_A7 (39), 14 = GND
+ MapPinNumber(1, 'B', 0), MapPinNumber(3, 'B', 5), // 15 = GPIO1_B0 (40), 16 = GPIO3_B5 (109)
+ -1, MapPinNumber(3, 'B', 6), // 17 = 3.3V, 18 = GPIO3_B6 (110)
+ MapPinNumber(1, 'B', 2), -1, // 19 = GPIO1_B2 (42), 20 = GND
+ MapPinNumber(1, 'B', 1), MapPinNumber(1, 'A', 2), // 21 = GPIO1_B1 (41), 22 = GPIO1_A2 (34)
+ MapPinNumber(1, 'B', 3), MapPinNumber(1, 'B', 4), // 23 = GPIO1_B3 (43), 24 = GPIO1_B4 (44)
+ -1, MapPinNumber(1, 'B', 5), // 25 = GND, 26 = GPIO1_B5 (45)
+ MapPinNumber(1, 'B', 7), MapPinNumber(1, 'B', 6), // 27 = GPIO1_B7 (47), 28 = GPIO1_B6 (46)
+ MapPinNumber(1, 'D', 7), -1, // 29 = GPIO1_D7 (63), 30 = GND
+ MapPinNumber(3, 'A', 0), MapPinNumber(1, 'A', 3), // 31 = GPIO3_A0 (96), 32 = GPIO1_A3 (35)
+ MapPinNumber(3, 'C', 2), -1, // 33 = GPIO3_C2 (114), 34 = GND
+ MapPinNumber(3, 'A', 2), MapPinNumber(3, 'A', 5), // 35 = GPIO3_A2 (98), 36 = GPIO3_A5 (101)
+ MapPinNumber(3, 'C', 1), MapPinNumber(3, 'A', 4), // 37 = GPIO3_C1 (113), 38 = GPIO3_A4 (100)
+ -1, MapPinNumber(3, 'A', 3), // 39 = GND, 40 = GPIO3_A3 (99)
+ };
+
+ ///
+ protected override int PinCount => 40;
+
+ ///
+ /// Maps a physical header pin number (1-40) to the driver's logical GPIO number.
+ ///
+ /// Physical pin number on the 40-pin header (1-40).
+ /// Logical GPIO pin number for use with .
+ /// The pin is not a GPIO pin (power, ground, etc.).
+ public static int MapPhysicalPinNumber(int physicalPin)
+ {
+ if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length)
+ {
+ throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-40).", nameof(physicalPin));
+ }
+
+ int gpio = _physicalToGpio[physicalPin];
+ return gpio != -1
+ ? gpio
+ : throw new ArgumentException($"Physical pin {physicalPin} is not a GPIO pin (power/ground).", nameof(physicalPin));
+ }
+ }
+}
diff --git a/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs b/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs
new file mode 100644
index 0000000000..2b23f680a9
--- /dev/null
+++ b/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Iot.Device.Gpio.Drivers
+{
+ ///
+ /// A GPIO driver for the Orange Pi 5 Pro (40-pin header).
+ ///
+ ///
+ /// SoC: Rockchip RK3588S
+ /// Pin mapping sourced from wiringOP (orangepi-xunlong/wiringOP).
+ ///
+ public class OrangePi5ProDriver : Rk3588Driver
+ {
+ // Mapping from physical header pin (index) to GPIO logical number.
+ // -1 indicates power, ground, or non-GPIO pins.
+ private static readonly int[] _physicalToGpio = new int[]
+ {
+ -1, // 0 (no pin)
+ -1, -1, // 1 = 3.3V, 2 = 5V
+ MapPinNumber(1, 'D', 3), -1, // 3 = GPIO1_D3 (59), 4 = 5V
+ MapPinNumber(1, 'D', 2), -1, // 5 = GPIO1_D2 (58), 6 = GND
+ MapPinNumber(1, 'B', 7), MapPinNumber(0, 'B', 5), // 7 = GPIO1_B7 (47), 8 = GPIO0_B5 (13)
+ -1, MapPinNumber(0, 'B', 6), // 9 = GND, 10 = GPIO0_B6 (14)
+ MapPinNumber(4, 'B', 2), MapPinNumber(1, 'A', 7), // 11 = GPIO4_B2 (138), 12 = GPIO1_A7 (39)
+ MapPinNumber(4, 'B', 3), -1, // 13 = GPIO4_B3 (139), 14 = GND
+ MapPinNumber(1, 'B', 6), MapPinNumber(1, 'A', 1), // 15 = GPIO1_B6 (46), 16 = GPIO1_A1 (33)
+ -1, MapPinNumber(1, 'A', 0), // 17 = 3.3V, 18 = GPIO1_A0 (32)
+ MapPinNumber(1, 'B', 2), -1, // 19 = GPIO1_B2 (42), 20 = GND
+ MapPinNumber(1, 'B', 1), MapPinNumber(1, 'B', 0), // 21 = GPIO1_B1 (41), 22 = GPIO1_B0 (40)
+ MapPinNumber(1, 'B', 3), MapPinNumber(1, 'B', 4), // 23 = GPIO1_B3 (43), 24 = GPIO1_B4 (44)
+ -1, MapPinNumber(1, 'B', 5), // 25 = GND, 26 = GPIO1_B5 (45)
+ MapPinNumber(1, 'A', 2), MapPinNumber(1, 'A', 3), // 27 = GPIO1_A2 (34), 28 = GPIO1_A3 (35)
+ MapPinNumber(1, 'A', 4), -1, // 29 = GPIO1_A4 (36), 30 = GND
+ MapPinNumber(1, 'A', 6), MapPinNumber(1, 'D', 6), // 31 = GPIO1_A6 (38), 32 = GPIO1_D6 (62)
+ MapPinNumber(1, 'D', 7), -1, // 33 = GPIO1_D7 (63), 34 = GND
+ MapPinNumber(4, 'A', 7), MapPinNumber(4, 'A', 3), // 35 = GPIO4_A7 (135), 36 = GPIO4_A3 (131)
+ MapPinNumber(4, 'A', 6), MapPinNumber(4, 'A', 4), // 37 = GPIO4_A6 (134), 38 = GPIO4_A4 (132)
+ -1, MapPinNumber(4, 'A', 5), // 39 = GND, 40 = GPIO4_A5 (133)
+ };
+
+ ///
+ protected override int PinCount => 40;
+
+ ///
+ /// Maps a physical header pin number (1-40) to the driver's logical GPIO number.
+ ///
+ /// Physical pin number on the 40-pin header (1-40).
+ /// Logical GPIO pin number for use with .
+ /// The pin is not a GPIO pin (power, ground, etc.).
+ public static int MapPhysicalPinNumber(int physicalPin)
+ {
+ if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length)
+ {
+ throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-40).", nameof(physicalPin));
+ }
+
+ int gpio = _physicalToGpio[physicalPin];
+ return gpio != -1
+ ? gpio
+ : throw new ArgumentException($"Physical pin {physicalPin} is not a GPIO pin (power/ground).", nameof(physicalPin));
+ }
+ }
+}
diff --git a/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs b/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs
new file mode 100644
index 0000000000..b0e7d203d0
--- /dev/null
+++ b/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Iot.Device.Gpio.Drivers
+{
+ ///
+ /// A GPIO driver for the Orange Pi 5 Ultra (40-pin header).
+ ///
+ ///
+ /// SoC: Rockchip RK3588S
+ /// Pin mapping sourced from wiringOP (orangepi-xunlong/wiringOP).
+ ///
+ public class OrangePi5UltraDriver : Rk3588Driver
+ {
+ // Mapping from physical header pin (index) to GPIO logical number.
+ // -1 indicates power, ground, or non-GPIO pins.
+ private static readonly int[] _physicalToGpio = new int[]
+ {
+ -1, // 0 (no pin)
+ -1, -1, // 1 = 3.3V, 2 = 5V
+ MapPinNumber(0, 'C', 0), -1, // 3 = GPIO0_C0 (16), 4 = 5V
+ MapPinNumber(0, 'B', 7), -1, // 5 = GPIO0_B7 (15), 6 = GND
+ MapPinNumber(1, 'A', 7), MapPinNumber(0, 'B', 5), // 7 = GPIO1_A7 (39), 8 = GPIO0_B5 (13)
+ -1, MapPinNumber(0, 'B', 6), // 9 = GND, 10 = GPIO0_B6 (14)
+ MapPinNumber(1, 'A', 0), MapPinNumber(4, 'A', 6), // 11 = GPIO1_A0 (32), 12 = GPIO4_A6 (134)
+ MapPinNumber(1, 'A', 1), -1, // 13 = GPIO1_A1 (33), 14 = GND
+ MapPinNumber(1, 'A', 2), MapPinNumber(1, 'A', 3), // 15 = GPIO1_A2 (34), 16 = GPIO1_A3 (35)
+ -1, MapPinNumber(1, 'A', 4), // 17 = 3.3V, 18 = GPIO1_A4 (36)
+ MapPinNumber(1, 'B', 2), -1, // 19 = GPIO1_B2 (42), 20 = GND
+ MapPinNumber(1, 'B', 1), MapPinNumber(1, 'B', 0), // 21 = GPIO1_B1 (41), 22 = GPIO1_B0 (40)
+ MapPinNumber(1, 'B', 3), MapPinNumber(1, 'B', 4), // 23 = GPIO1_B3 (43), 24 = GPIO1_B4 (44)
+ -1, MapPinNumber(1, 'B', 5), // 25 = GND, 26 = GPIO1_B5 (45)
+ MapPinNumber(4, 'C', 1), MapPinNumber(4, 'C', 0), // 27 = GPIO4_C1 (145), 28 = GPIO4_C0 (144)
+ MapPinNumber(3, 'C', 1), -1, // 29 = GPIO3_C1 (113), 30 = GND
+ MapPinNumber(3, 'B', 5), MapPinNumber(4, 'B', 3), // 31 = GPIO3_B5 (109), 32 = GPIO4_B3 (139)
+ MapPinNumber(3, 'B', 6), -1, // 33 = GPIO3_B6 (110), 34 = GND
+ MapPinNumber(3, 'C', 2), MapPinNumber(4, 'B', 7), // 35 = GPIO3_C2 (114), 36 = GPIO4_B7 (143)
+ MapPinNumber(4, 'A', 7), MapPinNumber(3, 'C', 0), // 37 = GPIO4_A7 (135), 38 = GPIO3_C0 (112)
+ -1, MapPinNumber(3, 'B', 7), // 39 = GND, 40 = GPIO3_B7 (111)
+ };
+
+ ///
+ protected override int PinCount => 40;
+
+ ///
+ /// Maps a physical header pin number (1-40) to the driver's logical GPIO number.
+ ///
+ /// Physical pin number on the 40-pin header (1-40).
+ /// Logical GPIO pin number for use with .
+ /// The pin is not a GPIO pin (power, ground, etc.).
+ public static int MapPhysicalPinNumber(int physicalPin)
+ {
+ if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length)
+ {
+ throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-40).", nameof(physicalPin));
+ }
+
+ int gpio = _physicalToGpio[physicalPin];
+ return gpio != -1
+ ? gpio
+ : throw new ArgumentException($"Physical pin {physicalPin} is not a GPIO pin (power/ground).", nameof(physicalPin));
+ }
+ }
+}
diff --git a/src/devices/Gpio/Drivers/Rockchip/README.md b/src/devices/Gpio/Drivers/Rockchip/README.md
index be0bb667a2..f3a80fb9b4 100644
--- a/src/devices/Gpio/Drivers/Rockchip/README.md
+++ b/src/devices/Gpio/Drivers/Rockchip/README.md
@@ -95,3 +95,63 @@ PinValue value = gpio.Read(pinNumber);
## References
Rockchip open source documents:
+
+## Supported SoCs and Boards
+
+### SoC Drivers
+
+| SoC | Driver |
+| :-: | :-: |
+| RK3328 | [Rk3328Driver](Rk3328Driver.cs) |
+| RK3399 | [Rk3399Driver](Rk3399Driver.cs) |
+| RK3588 / RK3588S | [Rk3588Driver](Rk3588Driver.cs) |
+
+### Board Drivers (RK3588 / RK3588S)
+
+| Board | SoC | Header Pins | Driver |
+| :-: | :-: | :-: | :-: |
+| Orange Pi 5 | RK3588S | 26-pin | [OrangePi5Driver](../OrangePi5Driver.cs) |
+| Orange Pi 5B | RK3588S | 26-pin | [OrangePi5BDriver](../OrangePi5BDriver.cs) |
+| Orange Pi 5 Plus | RK3588 | 40-pin | [OrangePi5PlusDriver](../OrangePi5PlusDriver.cs) |
+| Orange Pi 5 Pro | RK3588S | 40-pin | [OrangePi5ProDriver](../OrangePi5ProDriver.cs) |
+| Orange Pi 5 Max | RK3588 | 40-pin | [OrangePi5MaxDriver](../OrangePi5MaxDriver.cs) |
+| Orange Pi 5 Ultra | RK3588S | 40-pin | [OrangePi5UltraDriver](../OrangePi5UltraDriver.cs) |
+
+### Usage (Orange Pi 5 Pro)
+
+```csharp
+using System;
+using System.Device.Gpio;
+using Iot.Device.Gpio.Drivers;
+
+// Option 1: Map from physical header pin number (e.g. pin 19 on the 40-pin header)
+int pin = OrangePi5ProDriver.MapPhysicalPinNumber(19); // -> logical 42
+
+// Option 2: Map from GPIO name (e.g. GPIO1_B2 -> bank 1, port B, pin 2)
+int pin2 = RockchipDriver.MapPinNumber(gpioNumber: 1, port: 'B', portNumber: 2); // -> logical 42
+
+using GpioController controller = new GpioController(new OrangePi5ProDriver());
+
+// Output example
+controller.OpenPin(pin, PinMode.Output);
+controller.Write(pin, PinValue.High);
+
+// Input example with pull-up (reads High when idle, Low when shorted to GND)
+controller.OpenPin(pin, PinMode.InputPullUp);
+PinValue value = controller.Read(pin);
+```
+
+### Pin Number Mapping
+
+The RK3588 uses a naming convention of `GPIO{bank}_{port}{pin}` (e.g. `GPIO1_B2`). To convert to the logical pin number used by the driver:
+
+`logical = 32 * bank + 8 * port + pin`
+
+Where port A=0, B=1, C=2, D=3. For example, `GPIO1_B2` = 32×1 + 8×1 + 2 = **42**.
+
+You can use either helper:
+
+| Method | Example | Description |
+| :-- | :-- | :-- |
+| `RockchipDriver.MapPinNumber(1, 'B', 2)` | → 42 | From GPIO name (works with any Rockchip SoC) |
+| `OrangePi5ProDriver.MapPhysicalPinNumber(19)` | → 42 | From physical header pin (board-specific) |
diff --git a/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs b/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs
new file mode 100644
index 0000000000..1082f35b10
--- /dev/null
+++ b/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs
@@ -0,0 +1,526 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Device.Gpio;
+using System.IO;
+using System.Runtime.InteropServices;
+using static Interop;
+
+namespace Iot.Device.Gpio.Drivers
+{
+ ///
+ /// A GPIO driver for Rockchip RK3588.
+ ///
+ ///
+ ///
+ /// The RK3588 uses GPIO v2 registers with split LOW/HIGH data/direction registers
+ /// and built-in write-enable bits, unlike the v1 registers used by RK3328/RK3399.
+ ///
+ ///
+ /// IOMUX and pull-up/pull-down control is handled through multiple IOC (IO Controller)
+ /// domains: PMU1_IOC, PMU2_IOC, BUS_IOC, VCCIO1_4_IOC, VCCIO3_5_IOC, VCCIO2_IOC,
+ /// VCCIO6_IOC, and EMMC_IOC. The IOMUX uses 4-bit mux width for all GPIO banks.
+ ///
+ ///
+ /// The chip has 5 GPIO banks (GPIO0–GPIO4), each with 32 pins (4 ports × 8 pins).
+ ///
+ ///
+ public unsafe class Rk3588Driver : RockchipDriver
+ {
+ // GPIO v2 register offsets
+ private const int GPIO_SWPORT_DR_L = 0x0000;
+ private const int GPIO_SWPORT_DR_H = 0x0004;
+ private const int GPIO_SWPORT_DDR_L = 0x0008;
+ private const int GPIO_SWPORT_DDR_H = 0x000C;
+ private const int GPIO_EXT_PORT = 0x0070;
+
+ // IOC domain offsets (relative to IOC base 0xFD5F_0000)
+ private const int PMU1_IOC = 0x0000;
+ private const int PMU2_IOC = 0x4000;
+ private const int BUS_IOC = 0x8000;
+ private const int VCCIO1_4_IOC = 0x9000;
+ private const int VCCIO3_5_IOC = 0xA000;
+ private const int VCCIO2_IOC = 0xB000;
+ private const int VCCIO6_IOC = 0xC000;
+ private const int EMMC_IOC = 0xD000;
+
+ private static readonly int s_mappingLength = Environment.SystemPageSize * 16;
+
+ ///
+ /// Pull-up/pull-down control register offsets per port, indexed by GpioNumber * 4 + Port.
+ /// Derived from the Linux kernel rk3588_p_regs table in pinctrl-rockchip.c.
+ ///
+ ///
+ /// Some ports are split across two IOC domains (GPIO0_B, GPIO2_A, GPIO4_C).
+ /// The primary register for those ports is listed here; split pins are handled
+ /// by .
+ ///
+ private static readonly int[] _grfOffsets = new[]
+ {
+ PMU1_IOC + 0x0020, // GPIO0_A
+ PMU1_IOC + 0x0024, // GPIO0_B (B0–B4; B5–B7 → PMU2_IOC + 0x0028)
+ PMU2_IOC + 0x002C, // GPIO0_C
+ PMU2_IOC + 0x0030, // GPIO0_D
+ VCCIO1_4_IOC + 0x0110, // GPIO1_A
+ VCCIO1_4_IOC + 0x0114, // GPIO1_B
+ VCCIO1_4_IOC + 0x0118, // GPIO1_C
+ VCCIO1_4_IOC + 0x011C, // GPIO1_D
+ EMMC_IOC + 0x0120, // GPIO2_A (A0–A5; A6–A7 → VCCIO3_5_IOC + 0x0120)
+ VCCIO3_5_IOC + 0x0124, // GPIO2_B
+ VCCIO3_5_IOC + 0x0128, // GPIO2_C
+ EMMC_IOC + 0x012C, // GPIO2_D
+ VCCIO3_5_IOC + 0x0130, // GPIO3_A
+ VCCIO3_5_IOC + 0x0134, // GPIO3_B
+ VCCIO3_5_IOC + 0x0138, // GPIO3_C
+ VCCIO3_5_IOC + 0x013C, // GPIO3_D
+ VCCIO6_IOC + 0x0140, // GPIO4_A
+ VCCIO6_IOC + 0x0144, // GPIO4_B
+ VCCIO6_IOC + 0x0148, // GPIO4_C (C0–C1; C2–C7 → VCCIO3_5_IOC + 0x0148)
+ VCCIO2_IOC + 0x014C, // GPIO4_D
+ };
+
+ ///
+ /// IOMUX register offsets in BUS_IOC per port, indexed by GpioNumber * 4 + Port.
+ /// With 4-bit mux, each port has LOW (pins 0–3) and HIGH (pins 4–7) sub-registers.
+ /// The value points to the LOW register; the HIGH register is at offset + 4.
+ /// Layout: bank * 0x20 + port * 0x08 within BUS_IOC.
+ ///
+ private static readonly int[] _iomuxOffsets = new[]
+ {
+ BUS_IOC + 0x0000, // GPIO0_A
+ BUS_IOC + 0x0008, // GPIO0_B
+ BUS_IOC + 0x0010, // GPIO0_C
+ BUS_IOC + 0x0018, // GPIO0_D
+ BUS_IOC + 0x0020, // GPIO1_A
+ BUS_IOC + 0x0028, // GPIO1_B
+ BUS_IOC + 0x0030, // GPIO1_C
+ BUS_IOC + 0x0038, // GPIO1_D
+ BUS_IOC + 0x0040, // GPIO2_A
+ BUS_IOC + 0x0048, // GPIO2_B
+ BUS_IOC + 0x0050, // GPIO2_C
+ BUS_IOC + 0x0058, // GPIO2_D
+ BUS_IOC + 0x0060, // GPIO3_A
+ BUS_IOC + 0x0068, // GPIO3_B
+ BUS_IOC + 0x0070, // GPIO3_C
+ BUS_IOC + 0x0078, // GPIO3_D
+ BUS_IOC + 0x0080, // GPIO4_A
+ BUS_IOC + 0x0088, // GPIO4_B
+ BUS_IOC + 0x0090, // GPIO4_C
+ BUS_IOC + 0x0098, // GPIO4_D
+ };
+
+ ///
+ /// Additional PMU IOC IOMUX register offsets for GPIO0.
+ /// GPIO0 pins require writes to both BUS_IOC and the corresponding PMU IOC domain.
+ /// Indexed as [port, 0=LOW / 1=HIGH].
+ /// GPIO0_B HIGH is split: B4 uses PMU1_IOC, while B5–B7 use PMU2_IOC.
+ ///
+ private static readonly int[,] _gpio0PmuIomux = new int[,]
+ {
+ { PMU1_IOC + 0x0000, PMU1_IOC + 0x0004 }, // GPIO0_A: LOW, HIGH
+ { PMU1_IOC + 0x0008, PMU1_IOC + 0x000C }, // GPIO0_B: LOW (B0–B3), HIGH default (B4)
+ { PMU2_IOC + 0x0004, PMU2_IOC + 0x0008 }, // GPIO0_C: LOW, HIGH
+ { PMU2_IOC + 0x000C, PMU2_IOC + 0x0010 }, // GPIO0_D: LOW, HIGH
+ };
+
+ private static int GetGpio0PmuIomuxOffset(int port, int pin)
+ {
+ if (port == 1)
+ {
+ if (pin == 4)
+ {
+ return PMU1_IOC + 0x000C;
+ }
+
+ if (pin >= 5 && pin <= 7)
+ {
+ return PMU2_IOC + 0x0000;
+ }
+ }
+
+ return _gpio0PmuIomux[port, pin < 4 ? 0 : 1];
+ }
+
+ private IntPtr _iocPointer = IntPtr.Zero;
+ private IntPtr _cruPointer = IntPtr.Zero;
+ private IntPtr _pmuCruPointer = IntPtr.Zero;
+
+ ///
+ protected override uint[] GpioRegisterAddresses =>
+ new[] { 0xFD8A_0000, 0xFEC2_0000, 0xFEC3_0000, 0xFEC4_0000, 0xFEC5_0000 };
+
+ ///
+ /// IOC (IO Controller) base address. Covers all IOC domains (PMU1, PMU2, BUS, VCCIO*, EMMC).
+ ///
+ protected uint IoControllerBase => 0xFD5F_0000;
+
+ ///
+ /// Clock and Reset Unit (CRU) address. Controls GPIO1–GPIO4 clock gating.
+ ///
+ protected uint ClockResetUnit => 0xFD7C_0000;
+
+ ///
+ /// PMU Clock and Reset Unit (PMU CRU) address. Controls GPIO0 clock gating.
+ ///
+ protected uint PmuClockResetUnit => 0xFD7F_0000;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Rk3588Driver()
+ {
+ Initialize();
+ EnableGpio(true);
+ }
+
+ ///
+ protected override void Write(int pinNumber, PinValue value)
+ {
+ (int GpioNumber, int Port, int PortNumber) unmapped = UnmapPinNumber(pinNumber);
+ int bitIndex = unmapped.Port * 8 + unmapped.PortNumber;
+
+ // GPIO v2: use split LOW/HIGH data registers with write-enable in bits [31:16]
+ uint* dataPointer;
+ int bitOffset;
+
+ if (bitIndex < 16)
+ {
+ // LOW register: ports A and B (pins 0–15)
+ dataPointer = (uint*)(_gpioPointers[unmapped.GpioNumber] + GPIO_SWPORT_DR_L);
+ bitOffset = bitIndex;
+ }
+ else
+ {
+ // HIGH register: ports C and D (pins 16–31)
+ dataPointer = (uint*)(_gpioPointers[unmapped.GpioNumber] + GPIO_SWPORT_DR_H);
+ bitOffset = bitIndex - 16;
+ }
+
+ // Write-enable bit + data bit in a single atomic write
+ uint writeValue = 1U << (bitOffset + 16);
+ if (value == PinValue.High)
+ {
+ writeValue |= 1U << bitOffset;
+ }
+
+ *dataPointer = writeValue;
+ }
+
+ ///
+ protected override PinValue Read(int pinNumber)
+ {
+ (int GpioNumber, int Port, int PortNumber) unmapped = UnmapPinNumber(pinNumber);
+ int bitIndex = unmapped.Port * 8 + unmapped.PortNumber;
+
+ // GPIO v2: GPIO_EXT_PORT at offset 0x0070 (all 32 pins in one register)
+ uint* dataPointer = (uint*)(_gpioPointers[unmapped.GpioNumber] + GPIO_EXT_PORT);
+ uint dataValue = *dataPointer;
+
+ return Convert.ToBoolean((dataValue >> bitIndex) & 0b1) ? PinValue.High : PinValue.Low;
+ }
+
+ ///
+ protected override void SetPinMode(int pinNumber, PinMode mode)
+ {
+ (int GpioNumber, int Port, int PortNumber) unmapped = UnmapPinNumber(pinNumber);
+ int bitIndex = unmapped.Port * 8 + unmapped.PortNumber;
+ int portIndex = unmapped.GpioNumber * 4 + unmapped.Port;
+
+ // --- Set GPIO direction (GPIO v2: DDR_L / DDR_H with write-enable) ---
+ uint* dirPointer;
+ int dirBitOffset;
+
+ if (bitIndex < 16)
+ {
+ dirPointer = (uint*)(_gpioPointers[unmapped.GpioNumber] + GPIO_SWPORT_DDR_L);
+ dirBitOffset = bitIndex;
+ }
+ else
+ {
+ dirPointer = (uint*)(_gpioPointers[unmapped.GpioNumber] + GPIO_SWPORT_DDR_H);
+ dirBitOffset = bitIndex - 16;
+ }
+
+ // Write-enable for direction bit
+ uint dirValue = 1U << (dirBitOffset + 16);
+ switch (mode)
+ {
+ case PinMode.Input:
+ case PinMode.InputPullDown:
+ case PinMode.InputPullUp:
+ // input = 0; write-enable is set, data bit stays 0
+ break;
+ case PinMode.Output:
+ // output = 1
+ dirValue |= 1U << dirBitOffset;
+ break;
+ default:
+ break;
+ }
+
+ // --- Set IOMUX to GPIO mode (4-bit mux per pin, GPIO = 0x0) ---
+ int iomuxBaseOffset = _iomuxOffsets[portIndex];
+ int iomuxBitOffset;
+ uint* iomuxPointer;
+
+ if (unmapped.PortNumber < 4)
+ {
+ // LOW register (pins 0–3 of the port)
+ iomuxPointer = (uint*)(_iocPointer + iomuxBaseOffset);
+ iomuxBitOffset = unmapped.PortNumber * 4;
+ }
+ else
+ {
+ // HIGH register (pins 4–7 of the port), at base + 4
+ iomuxPointer = (uint*)(_iocPointer + iomuxBaseOffset + 4);
+ iomuxBitOffset = (unmapped.PortNumber - 4) * 4;
+ }
+
+ // Write-enable for 4 mux bits, data = 0 (GPIO mode).
+ // These registers use write-enable in [31:16]; do NOT read before write.
+ uint iomuxValue = 0b1111U << (iomuxBitOffset + 16);
+
+ // --- For GPIO0, also write to PMU IOC IOMUX register ---
+ uint* pmuIomuxPointer = null;
+ uint pmuIomuxValue = 0;
+
+ if (unmapped.GpioNumber == 0)
+ {
+ int pmuIomuxOffset = GetGpio0PmuIomuxOffset(unmapped.Port, unmapped.PortNumber);
+
+ pmuIomuxPointer = (uint*)(_iocPointer + pmuIomuxOffset);
+ // Write-enable for 4 mux bits, data = 0 (GPIO mode)
+ pmuIomuxValue = 0b1111U << (iomuxBitOffset + 16);
+ }
+
+ // --- Set pull-up / pull-down ---
+ GetPullRegisterAndBit(unmapped.GpioNumber, unmapped.Port, unmapped.PortNumber, out int pullOffset, out int pullBitOffset);
+
+ uint* pullPointer = (uint*)(_iocPointer + pullOffset);
+ // Write-enable for 2 pull bits; do NOT read before write.
+ // RK3588 pull encoding (PULL_TYPE_IO_1V8_ONLY): none = 0b00; pull-down = 0b01; pull-up = 0b11
+ uint pullValue = 0b11U << (pullBitOffset + 16);
+ switch (mode)
+ {
+ case PinMode.InputPullUp:
+ pullValue |= 0b11U << pullBitOffset;
+ break;
+ case PinMode.InputPullDown:
+ pullValue |= 0b01U << pullBitOffset;
+ break;
+ default:
+ break;
+ }
+
+ // Write registers in order: IOMUX, pull, direction
+ *iomuxPointer = iomuxValue;
+ if (pmuIomuxPointer != null)
+ {
+ *pmuIomuxPointer = pmuIomuxValue;
+ }
+
+ *pullPointer = pullValue;
+ *dirPointer = dirValue;
+
+ if (_pinModes.ContainsKey(pinNumber))
+ {
+ _pinModes[pinNumber].CurrentPinMode = mode;
+ }
+ else
+ {
+ _pinModes.Add(pinNumber, new PinState(mode));
+ }
+ }
+
+ ///
+ protected override bool IsPinModeSupported(int pinNumber, PinMode mode)
+ {
+ return mode switch
+ {
+ PinMode.Input or PinMode.Output or PinMode.InputPullUp or PinMode.InputPullDown => true,
+ _ => false,
+ };
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ EnableGpio(false);
+
+ if (_iocPointer != IntPtr.Zero)
+ {
+ Interop.munmap(_iocPointer, s_mappingLength);
+ _iocPointer = IntPtr.Zero;
+ }
+
+ if (_cruPointer != IntPtr.Zero)
+ {
+ Interop.munmap(_cruPointer, s_mappingLength);
+ _cruPointer = IntPtr.Zero;
+ }
+
+ if (_pmuCruPointer != IntPtr.Zero)
+ {
+ Interop.munmap(_pmuCruPointer, s_mappingLength);
+ _pmuCruPointer = IntPtr.Zero;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ ///
+ /// Resolves the IOC pull register offset and bit position for a given pin.
+ /// Handles split ports where some pins within a port span two IOC domains.
+ ///
+ private static void GetPullRegisterAndBit(int gpioNumber, int port, int portNumber, out int registerOffset, out int bitOffset)
+ {
+ // GPIO0_B: B0–B4 in PMU1_IOC, B5–B7 in PMU2_IOC
+ if (gpioNumber == 0 && port == 1 && portNumber >= 5)
+ {
+ registerOffset = PMU2_IOC + 0x0028;
+ bitOffset = (portNumber - 5) * 2;
+ return;
+ }
+
+ // GPIO2_A: A0–A5 in EMMC_IOC, A6–A7 in VCCIO3_5_IOC
+ if (gpioNumber == 2 && port == 0 && portNumber >= 6)
+ {
+ registerOffset = VCCIO3_5_IOC + 0x0120;
+ bitOffset = (portNumber - 6) * 2;
+ return;
+ }
+
+ // GPIO4_C: C0–C1 in VCCIO6_IOC, C2–C7 in VCCIO3_5_IOC
+ if (gpioNumber == 4 && port == 2 && portNumber >= 2)
+ {
+ registerOffset = VCCIO3_5_IOC + 0x0148;
+ bitOffset = (portNumber - 2) * 2;
+ return;
+ }
+
+ // Normal case: single register per port
+ registerOffset = _grfOffsets[gpioNumber * 4 + port];
+ bitOffset = portNumber * 2;
+ }
+
+ private void EnableGpio(bool enable)
+ {
+ // CRU gate registers use write-enable in [31:16]; do NOT read before write.
+ // When gate bit is HIGH, clock is gated (disabled); LOW = enabled.
+ //
+ // Register and bit assignments from Linux kernel clk-rk3588.c:
+ // GPIO0: PMU_CLKGATE_CON(5) offset 0x0814, bit 5 = PCLK_GPIO0, bit 6 = DBCLK_GPIO0
+ // GPIO1: CRU CLKGATE_CON(16) offset 0x0840, bit 14 = PCLK_GPIO1, bit 15 = DBCLK_GPIO1
+ // GPIO2: CRU CLKGATE_CON(17) offset 0x0844, bit 0/1 = PCLK/DBCLK_GPIO2
+ // GPIO3: CRU CLKGATE_CON(17) offset 0x0844, bit 2/3 = PCLK/DBCLK_GPIO3
+ // GPIO4: CRU CLKGATE_CON(17) offset 0x0844, bit 4/5 = PCLK/DBCLK_GPIO4
+ if (enable)
+ {
+ // GPIO0: PMU CRU CLKGATE_CON(5), write-enable bits 5-6, data = 0 (enable)
+ uint* pmuCruGate5 = (uint*)(_pmuCruPointer + 0x0814);
+ *pmuCruGate5 = 0b11U << (5 + 16);
+
+ // GPIO1: CRU CLKGATE_CON(16), write-enable bits 14-15, data = 0 (enable)
+ uint* cruGate16 = (uint*)(_cruPointer + 0x0840);
+ *cruGate16 = 0b11U << (14 + 16);
+
+ // GPIO2–4: CRU CLKGATE_CON(17), write-enable bits 0-5, data = 0 (enable)
+ uint* cruGate17 = (uint*)(_cruPointer + 0x0844);
+ *cruGate17 = 0b111111U << 16;
+ }
+
+ // Do NOT gate GPIO clocks on disable/dispose.
+ // Other kernel drivers may depend on these clocks, and
+ // PMU_CLKGATE_CON(5) shares bits with critical system clocks (PCLK_PMU0_ROOT).
+ // The kernel's clock framework will manage gating as needed.
+ }
+
+ private void Initialize()
+ {
+ if (_iocPointer != IntPtr.Zero)
+ {
+ return;
+ }
+
+ lock (s_initializationLock)
+ {
+ if (_iocPointer != IntPtr.Zero)
+ {
+ return;
+ }
+
+ int fileDescriptor = -1;
+ int mapLength = s_mappingLength;
+ IntPtr iocMap = IntPtr.Zero;
+ IntPtr cruMap = IntPtr.Zero;
+ IntPtr pmuCruMap = IntPtr.Zero;
+ IntPtr mapFailed = new IntPtr(-1);
+ bool success = false;
+
+ try
+ {
+ fileDescriptor = Interop.open(GpioMemoryFilePath, FileOpenFlags.O_RDWR | FileOpenFlags.O_SYNC);
+ if (fileDescriptor == -1)
+ {
+ throw new IOException($"Error {Marshal.GetLastWin32Error()} initializing the Gpio driver.");
+ }
+
+ // IOC register region: covers all IOC domains (PMU1 through EMMC, ~56 KB)
+ iocMap = Interop.mmap(IntPtr.Zero, mapLength, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, MemoryMappedFlags.MAP_SHARED, fileDescriptor, (int)(IoControllerBase & ~_mapMask));
+ if (iocMap == mapFailed)
+ {
+ int error = Marshal.GetLastWin32Error();
+ throw new IOException($"Error {error} initializing the Gpio driver (IOC initialize error).");
+ }
+
+ // CRU register region: clock gating for GPIO1–GPIO4 (CLKGATE_CON9 at offset 0x0824)
+ cruMap = Interop.mmap(IntPtr.Zero, mapLength, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, MemoryMappedFlags.MAP_SHARED, fileDescriptor, (int)(ClockResetUnit & ~_mapMask));
+ if (cruMap == mapFailed)
+ {
+ int error = Marshal.GetLastWin32Error();
+ throw new IOException($"Error {error} initializing the Gpio driver (CRU initialize error).");
+ }
+
+ // PMU CRU register region: clock gating for GPIO0 (PMU_CLKGATE_CON5 at offset 0x0814)
+ pmuCruMap = Interop.mmap(IntPtr.Zero, mapLength, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, MemoryMappedFlags.MAP_SHARED, fileDescriptor, (int)(PmuClockResetUnit & ~_mapMask));
+ if (pmuCruMap == mapFailed)
+ {
+ int error = Marshal.GetLastWin32Error();
+ throw new IOException($"Error {error} initializing the Gpio driver (PMU CRU initialize error).");
+ }
+
+ _iocPointer = iocMap;
+ _cruPointer = cruMap;
+ _pmuCruPointer = pmuCruMap;
+ success = true;
+ }
+ finally
+ {
+ if (fileDescriptor != -1)
+ {
+ Interop.close(fileDescriptor);
+ }
+
+ if (!success)
+ {
+ if (pmuCruMap != IntPtr.Zero && pmuCruMap != mapFailed)
+ {
+ Interop.munmap(pmuCruMap, mapLength);
+ }
+
+ if (cruMap != IntPtr.Zero && cruMap != mapFailed)
+ {
+ Interop.munmap(cruMap, mapLength);
+ }
+
+ if (iocMap != IntPtr.Zero && iocMap != mapFailed)
+ {
+ Interop.munmap(iocMap, mapLength);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/devices/Gpio/Gpio.sln b/src/devices/Gpio/Gpio.sln
index 3900addd02..fc3d193888 100644
--- a/src/devices/Gpio/Gpio.sln
+++ b/src/devices/Gpio/Gpio.sln
@@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gpio.Tests", "tests\Gpio.Te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Board", "..\Board\Board.csproj", "{BB45F1F6-25AB-43E4-945E-F2B9673EA684}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Iot.Device.Gpio.Samples.OrangePi5", "samples\orangepi5\Iot.Device.Gpio.Samples.OrangePi5.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -101,12 +103,25 @@ Global
{BB45F1F6-25AB-43E4-945E-F2B9673EA684}.Release|x64.Build.0 = Release|Any CPU
{BB45F1F6-25AB-43E4-945E-F2B9673EA684}.Release|x86.ActiveCfg = Release|Any CPU
{BB45F1F6-25AB-43E4-945E-F2B9673EA684}.Release|x86.Build.0 = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{455A62CA-C3D1-40AA-BD70-985E14129A92} = {45EC0518-E2FF-4278-ABDB-E0A2D79E71BE}
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {45EC0518-E2FF-4278-ABDB-E0A2D79E71BE}
{02FB3A2F-CA90-4ACA-8A83-F270E13BD07C} = {9CF7DED7-33FE-4177-95CC-626CA3E2B5B2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/src/devices/Gpio/README.md b/src/devices/Gpio/README.md
index 5f1751afcf..79aace092e 100644
--- a/src/devices/Gpio/README.md
+++ b/src/devices/Gpio/README.md
@@ -32,6 +32,12 @@ When adding an existing GpioPin, by default, the new pin allocation will directl
| Board | Driver |
| :-: | :-: |
| Orange Pi 4 | [OrangePi4Driver](./Drivers/OrangePi4Driver.cs) |
+| Orange Pi 5 | [OrangePi5Driver](./Drivers/OrangePi5Driver.cs) |
+| Orange Pi 5B | [OrangePi5BDriver](./Drivers/OrangePi5BDriver.cs) |
+| Orange Pi 5 Plus | [OrangePi5PlusDriver](./Drivers/OrangePi5PlusDriver.cs) |
+| Orange Pi 5 Pro | [OrangePi5ProDriver](./Drivers/OrangePi5ProDriver.cs) |
+| Orange Pi 5 Max | [OrangePi5MaxDriver](./Drivers/OrangePi5MaxDriver.cs) |
+| Orange Pi 5 Ultra | [OrangePi5UltraDriver](./Drivers/OrangePi5UltraDriver.cs) |
| Orange Pi Lite | [OrangePiLiteDriver](./Drivers/OrangePiLiteDriver.cs) |
| Orange Pi Lite 2 | [OrangePiLite2Driver](./Drivers/OrangePiLite2Driver.cs) |
| Orange Pi Zero | [OrangePiZeroDriver](./Drivers/OrangePiZeroDriver.cs) |
diff --git a/src/devices/Gpio/samples/Iot.Device.Gpio.Samples.csproj b/src/devices/Gpio/samples/Iot.Device.Gpio.Samples.csproj
index 56500b9db8..b9f6405400 100644
--- a/src/devices/Gpio/samples/Iot.Device.Gpio.Samples.csproj
+++ b/src/devices/Gpio/samples/Iot.Device.Gpio.Samples.csproj
@@ -8,6 +8,7 @@
+
diff --git a/src/devices/Gpio/samples/orangepi5/Iot.Device.Gpio.Samples.OrangePi5.csproj b/src/devices/Gpio/samples/orangepi5/Iot.Device.Gpio.Samples.OrangePi5.csproj
new file mode 100644
index 0000000000..28f76eafd6
--- /dev/null
+++ b/src/devices/Gpio/samples/orangepi5/Iot.Device.Gpio.Samples.OrangePi5.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ $(DefaultSampleTfms)
+ enable
+
+
+
+
+
+
+
+
diff --git a/src/devices/Gpio/samples/orangepi5/Program.cs b/src/devices/Gpio/samples/orangepi5/Program.cs
new file mode 100644
index 0000000000..1bfb584e72
--- /dev/null
+++ b/src/devices/Gpio/samples/orangepi5/Program.cs
@@ -0,0 +1,70 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Device.Gpio;
+using System.Threading;
+using Iot.Device.BoardLed;
+using Iot.Device.Gpio.Drivers;
+
+namespace Sunxi.Gpio.Samples
+{
+ internal class Program
+ {
+ private static void Main(string[] args)
+ {
+ // Physical header pin 19 = GPIO1_B2 (logical 42) — safe, not in PMU domain.
+ // Avoid GPIO0 pins (physical 8, 10) as they may control the PMIC.
+ int physicalPin = 19;
+ int pin = OrangePi5ProDriver.MapPhysicalPinNumber(physicalPin);
+ bool outputMode = args.Length > 0 && args[0] == "--output";
+
+ Console.WriteLine($"Physical pin {physicalPin} -> GPIO logical pin {pin}");
+
+ using GpioController controller = new GpioController(new OrangePi5ProDriver());
+ using BoardLed led = new BoardLed("blue_led");
+ led.Trigger = "none";
+
+ if (outputMode)
+ {
+ // Output test: toggle the pin HIGH/LOW every second.
+ // Measure voltage on pin 19 with a multimeter — should alternate ~3.3V / 0V.
+ controller.OpenPin(pin, PinMode.Output);
+ Console.WriteLine($"Output mode: toggling pin {pin} (press any key to exit)...");
+ bool high = false;
+
+ while (!Console.KeyAvailable)
+ {
+ high = !high;
+ controller.Write(pin, high ? PinValue.High : PinValue.Low);
+ led.Brightness = high ? 1 : 0;
+ Console.WriteLine($"Pin = {(high ? "HIGH" : "LOW")}");
+ Thread.Sleep(1000);
+ }
+ }
+ else
+ {
+ // Input test: read pin state in a simple loop.
+ // With InputPullUp, pin reads High when idle, Low when shorted to GND.
+ controller.OpenPin(pin, PinMode.InputPullUp);
+ Console.WriteLine($"Input mode: reading pin {pin} (press any key to exit)...");
+ PinValue lastState = controller.Read(pin);
+ Console.WriteLine($"Initial state: {lastState}");
+
+ while (!Console.KeyAvailable)
+ {
+ PinValue currentState = controller.Read(pin);
+
+ if (currentState != lastState)
+ {
+ Console.WriteLine($"State changed: {currentState}");
+ led.Brightness = currentState == PinValue.Low ? 1 : 0;
+ lastState = currentState;
+ }
+
+ Thread.Sleep(50);
+ }
+ }
+ }
+ }
+}