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); + } + } + } + } +}