From f48d218accb62208651aa824d8e7d15b55da1ba4 Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Fri, 13 Feb 2026 09:58:25 +0100 Subject: [PATCH 1/7] add RK3588 --- .../Gpio/Drivers/Rockchip/Rk3588Driver.cs | 483 ++++++++++++++++++ 1 file changed, 483 insertions(+) create mode 100644 src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs diff --git a/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs b/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs new file mode 100644 index 0000000000..103665e18a --- /dev/null +++ b/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs @@ -0,0 +1,483 @@ +// 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; + + /// + /// 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 in PMU1_IOC, B5–B7 in PMU2_IOC (handled as PMU2). + /// + private static readonly int[,] _gpio0PmuIomux = new int[,] + { + { PMU1_IOC + 0x0000, PMU1_IOC + 0x0004 }, // GPIO0_A: LOW, HIGH + { PMU1_IOC + 0x0008, PMU2_IOC + 0x0000 }, // GPIO0_B: LOW (B0–B3), HIGH (B4–B7) + { PMU2_IOC + 0x0004, PMU2_IOC + 0x0008 }, // GPIO0_C: LOW, HIGH + { PMU2_IOC + 0x000C, PMU2_IOC + 0x0010 }, // GPIO0_D: LOW, HIGH + }; + + 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; + } + + uint iomuxValue = *iomuxPointer; + // write-enable for 4 mux bits + iomuxValue |= 0b1111U << (iomuxBitOffset + 16); + // clear mux to 0 (GPIO mode) + iomuxValue &= ~(0b1111U << iomuxBitOffset); + + // --- For GPIO0, also write to PMU IOC IOMUX register --- + uint* pmuIomuxPointer = null; + uint pmuIomuxValue = 0; + + if (unmapped.GpioNumber == 0) + { + int pmuIomuxOffset = unmapped.PortNumber < 4 + ? _gpio0PmuIomux[unmapped.Port, 0] + : _gpio0PmuIomux[unmapped.Port, 1]; + + pmuIomuxPointer = (uint*)(_iocPointer + pmuIomuxOffset); + pmuIomuxValue = *pmuIomuxPointer; + pmuIomuxValue |= 0b1111U << (iomuxBitOffset + 16); + pmuIomuxValue &= ~(0b1111U << iomuxBitOffset); + } + + // --- Set pull-up / pull-down --- + GetPullRegisterAndBit(unmapped.GpioNumber, unmapped.Port, unmapped.PortNumber, out int pullOffset, out int pullBitOffset); + + uint* pullPointer = (uint*)(_iocPointer + pullOffset); + uint pullValue = *pullPointer; + // write-enable for 2 pull bits + pullValue |= 0b11U << (pullBitOffset + 16); + // clear pull bits first + pullValue &= ~(0b11U << pullBitOffset); + // RK3588 pull encoding (PULL_TYPE_IO_1V8_ONLY): pull-up = 0b01; pull-down = 0b10; none = 0b00 + switch (mode) + { + case PinMode.InputPullUp: + pullValue |= 0b01U << pullBitOffset; + break; + case PinMode.InputPullDown: + pullValue |= 0b10U << 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, 0); + _iocPointer = IntPtr.Zero; + } + + if (_cruPointer != IntPtr.Zero) + { + Interop.munmap(_cruPointer, 0); + _cruPointer = IntPtr.Zero; + } + + if (_pmuCruPointer != IntPtr.Zero) + { + Interop.munmap(_pmuCruPointer, 0); + _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) + { + uint* pmuCruGatePointer, cruGatePointer; + uint pmuCruValue, cruValue; + + // PMU CRU CLKGATE_CON5 offset is 0x0814 (GPIO0 pclk and dbclk) + // Bit 0: PCLK_GPIO0, Bit 1: DBCLK_GPIO0 + pmuCruGatePointer = (uint*)(_pmuCruPointer + 0x0814); + pmuCruValue = *pmuCruGatePointer; + + // CRU CLKGATE_CON9 offset is 0x0824 (GPIO1–GPIO4 pclk and dbclk) + // Bits 0–3: PCLK_GPIO1–4, Bits 4–7: DBCLK_GPIO1–4 + cruGatePointer = (uint*)(_cruPointer + 0x0824); + cruValue = *cruGatePointer; + + // software write enable + pmuCruValue |= 0b11U << 16; + cruValue |= 0xFFU << 16; + + if (enable) + { + // when HIGH, clock is gated (disabled); clear to enable + pmuCruValue &= ~0b11U; + cruValue &= ~0xFFU; + } + else + { + pmuCruValue |= 0b11U; + cruValue |= 0xFFU; + } + + *pmuCruGatePointer = pmuCruValue; + *cruGatePointer = cruValue; + } + + private void Initialize() + { + if (_iocPointer != IntPtr.Zero) + { + return; + } + + lock (s_initializationLock) + { + if (_iocPointer != IntPtr.Zero) + { + return; + } + + int 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) + IntPtr iocMap = Interop.mmap(IntPtr.Zero, Environment.SystemPageSize * 16, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, MemoryMappedFlags.MAP_SHARED, fileDescriptor, (int)(IoControllerBase & ~_mapMask)); + // CRU register region: clock gating for GPIO1–GPIO4 (CLKGATE_CON9 at offset 0x0824) + IntPtr cruMap = Interop.mmap(IntPtr.Zero, Environment.SystemPageSize * 16, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, MemoryMappedFlags.MAP_SHARED, fileDescriptor, (int)(ClockResetUnit & ~_mapMask)); + // PMU CRU register region: clock gating for GPIO0 (PMU_CLKGATE_CON5 at offset 0x0814) + IntPtr pmuCruMap = Interop.mmap(IntPtr.Zero, Environment.SystemPageSize * 16, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, MemoryMappedFlags.MAP_SHARED, fileDescriptor, (int)(PmuClockResetUnit & ~_mapMask)); + + if (iocMap.ToInt64() == -1) + { + Interop.munmap(iocMap, 0); + throw new IOException($"Error {Marshal.GetLastWin32Error()} initializing the Gpio driver (IOC initialize error)."); + } + + if (cruMap.ToInt64() == -1) + { + Interop.munmap(cruMap, 0); + throw new IOException($"Error {Marshal.GetLastWin32Error()} initializing the Gpio driver (CRU initialize error)."); + } + + if (pmuCruMap.ToInt64() == -1) + { + Interop.munmap(pmuCruMap, 0); + throw new IOException($"Error {Marshal.GetLastWin32Error()} initializing the Gpio driver (PMU CRU initialize error)."); + } + + _iocPointer = iocMap; + _cruPointer = cruMap; + _pmuCruPointer = pmuCruMap; + + Interop.close(fileDescriptor); + } + } + } +} From ad5dd2a9de7b63ed81cd12ebff687409071d9165 Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Fri, 13 Feb 2026 10:14:37 +0100 Subject: [PATCH 2/7] adding Orange Pi 5 drivers --- src/devices/Gpio/Drivers/OrangePi5BDriver.cs | 19 +++++++++++++++++++ src/devices/Gpio/Drivers/OrangePi5Driver.cs | 19 +++++++++++++++++++ .../Gpio/Drivers/OrangePi5MaxDriver.cs | 19 +++++++++++++++++++ .../Gpio/Drivers/OrangePi5PlusDriver.cs | 19 +++++++++++++++++++ .../Gpio/Drivers/OrangePi5ProDriver.cs | 19 +++++++++++++++++++ .../Gpio/Drivers/OrangePi5UltraDriver.cs | 19 +++++++++++++++++++ 6 files changed, 114 insertions(+) create mode 100644 src/devices/Gpio/Drivers/OrangePi5BDriver.cs create mode 100644 src/devices/Gpio/Drivers/OrangePi5Driver.cs create mode 100644 src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs create mode 100644 src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs create mode 100644 src/devices/Gpio/Drivers/OrangePi5ProDriver.cs create mode 100644 src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs diff --git a/src/devices/Gpio/Drivers/OrangePi5BDriver.cs b/src/devices/Gpio/Drivers/OrangePi5BDriver.cs new file mode 100644 index 0000000000..bdf0c683d3 --- /dev/null +++ b/src/devices/Gpio/Drivers/OrangePi5BDriver.cs @@ -0,0 +1,19 @@ +// 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. + /// + /// + /// SoC: Rockchip RK3588S + /// + public class OrangePi5BDriver : Rk3588Driver + { + /// + protected override int PinCount => 26; + } +} diff --git a/src/devices/Gpio/Drivers/OrangePi5Driver.cs b/src/devices/Gpio/Drivers/OrangePi5Driver.cs new file mode 100644 index 0000000000..6b9e4cf279 --- /dev/null +++ b/src/devices/Gpio/Drivers/OrangePi5Driver.cs @@ -0,0 +1,19 @@ +// 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. + /// + /// + /// SoC: Rockchip RK3588S + /// + public class OrangePi5Driver : Rk3588Driver + { + /// + protected override int PinCount => 26; + } +} diff --git a/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs b/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs new file mode 100644 index 0000000000..0cf47c134e --- /dev/null +++ b/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs @@ -0,0 +1,19 @@ +// 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. + /// + /// + /// SoC: Rockchip RK3588 + /// + public class OrangePi5MaxDriver : Rk3588Driver + { + /// + protected override int PinCount => 40; + } +} diff --git a/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs b/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs new file mode 100644 index 0000000000..f1ef49444d --- /dev/null +++ b/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs @@ -0,0 +1,19 @@ +// 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. + /// + /// + /// SoC: Rockchip RK3588 + /// + public class OrangePi5PlusDriver : Rk3588Driver + { + /// + protected override int PinCount => 40; + } +} diff --git a/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs b/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs new file mode 100644 index 0000000000..cb827f8dc5 --- /dev/null +++ b/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs @@ -0,0 +1,19 @@ +// 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. + /// + /// + /// SoC: Rockchip RK3588S + /// + public class OrangePi5ProDriver : Rk3588Driver + { + /// + protected override int PinCount => 40; + } +} diff --git a/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs b/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs new file mode 100644 index 0000000000..bfd78d55d2 --- /dev/null +++ b/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs @@ -0,0 +1,19 @@ +// 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. + /// + /// + /// SoC: Rockchip RK3588 + /// + public class OrangePi5UltraDriver : Rk3588Driver + { + /// + protected override int PinCount => 40; + } +} From 578b6e2dfeaf30c2e40332b8867f54728766023a Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Sat, 18 Apr 2026 16:12:04 +0200 Subject: [PATCH 3/7] adding orangepi 5 drivers --- src/devices/Gpio/Drivers/OrangePi5BDriver.cs | 44 ++++++++++- src/devices/Gpio/Drivers/OrangePi5Driver.cs | 44 ++++++++++- .../Gpio/Drivers/OrangePi5MaxDriver.cs | 51 +++++++++++- .../Gpio/Drivers/OrangePi5PlusDriver.cs | 51 +++++++++++- .../Gpio/Drivers/OrangePi5ProDriver.cs | 51 +++++++++++- .../Gpio/Drivers/OrangePi5UltraDriver.cs | 51 +++++++++++- src/devices/Gpio/Drivers/Rockchip/README.md | 60 ++++++++++++++ .../Gpio/Drivers/Rockchip/Rk3588Driver.cs | 79 ++++++++----------- src/devices/Gpio/Gpio.sln | 15 ++++ src/devices/Gpio/README.md | 6 ++ .../samples/Iot.Device.Gpio.Samples.csproj | 1 + .../Iot.Device.Gpio.Samples.OrangePi5.csproj | 14 ++++ src/devices/Gpio/samples/orangepi5/Program.cs | 70 ++++++++++++++++ 13 files changed, 481 insertions(+), 56 deletions(-) create mode 100644 src/devices/Gpio/samples/orangepi5/Iot.Device.Gpio.Samples.OrangePi5.csproj create mode 100644 src/devices/Gpio/samples/orangepi5/Program.cs diff --git a/src/devices/Gpio/Drivers/OrangePi5BDriver.cs b/src/devices/Gpio/Drivers/OrangePi5BDriver.cs index bdf0c683d3..9259c9256b 100644 --- a/src/devices/Gpio/Drivers/OrangePi5BDriver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5BDriver.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -6,14 +6,54 @@ namespace Iot.Device.Gpio.Drivers { /// - /// A GPIO driver for the Orange Pi 5B. + /// 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 index 6b9e4cf279..58f720a0df 100644 --- a/src/devices/Gpio/Drivers/OrangePi5Driver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5Driver.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -6,14 +6,54 @@ namespace Iot.Device.Gpio.Drivers { /// - /// A GPIO driver for the Orange Pi 5. + /// 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 index 0cf47c134e..7195c8a73e 100644 --- a/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -6,14 +6,61 @@ namespace Iot.Device.Gpio.Drivers { /// - /// A GPIO driver for the Orange Pi 5 Max. + /// 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 index f1ef49444d..90700b72dc 100644 --- a/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -6,14 +6,61 @@ namespace Iot.Device.Gpio.Drivers { /// - /// A GPIO driver for the Orange Pi 5 Plus. + /// 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 index cb827f8dc5..8c65ed6435 100644 --- a/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -6,14 +6,61 @@ namespace Iot.Device.Gpio.Drivers { /// - /// A GPIO driver for the Orange Pi 5 Pro. + /// 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 index bfd78d55d2..e84540b58f 100644 --- a/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -6,14 +6,61 @@ namespace Iot.Device.Gpio.Drivers { /// - /// A GPIO driver for the Orange Pi 5 Ultra. + /// A GPIO driver for the Orange Pi 5 Ultra (40-pin header). /// /// /// SoC: Rockchip RK3588 + /// 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 index 103665e18a..0635f48404 100644 --- a/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs +++ b/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -257,11 +257,9 @@ protected override void SetPinMode(int pinNumber, PinMode mode) iomuxBitOffset = (unmapped.PortNumber - 4) * 4; } - uint iomuxValue = *iomuxPointer; - // write-enable for 4 mux bits - iomuxValue |= 0b1111U << (iomuxBitOffset + 16); - // clear mux to 0 (GPIO mode) - iomuxValue &= ~(0b1111U << iomuxBitOffset); + // 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; @@ -274,28 +272,24 @@ protected override void SetPinMode(int pinNumber, PinMode mode) : _gpio0PmuIomux[unmapped.Port, 1]; pmuIomuxPointer = (uint*)(_iocPointer + pmuIomuxOffset); - pmuIomuxValue = *pmuIomuxPointer; - pmuIomuxValue |= 0b1111U << (iomuxBitOffset + 16); - pmuIomuxValue &= ~(0b1111U << iomuxBitOffset); + // 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); - uint pullValue = *pullPointer; - // write-enable for 2 pull bits - pullValue |= 0b11U << (pullBitOffset + 16); - // clear pull bits first - pullValue &= ~(0b11U << pullBitOffset); - // RK3588 pull encoding (PULL_TYPE_IO_1V8_ONLY): pull-up = 0b01; pull-down = 0b10; none = 0b00 + // 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 |= 0b01U << pullBitOffset; + pullValue |= 0b11U << pullBitOffset; break; case PinMode.InputPullDown: - pullValue |= 0b10U << pullBitOffset; + pullValue |= 0b01U << pullBitOffset; break; default: break; @@ -394,37 +388,34 @@ private static void GetPullRegisterAndBit(int gpioNumber, int port, int portNumb private void EnableGpio(bool enable) { - uint* pmuCruGatePointer, cruGatePointer; - uint pmuCruValue, cruValue; - - // PMU CRU CLKGATE_CON5 offset is 0x0814 (GPIO0 pclk and dbclk) - // Bit 0: PCLK_GPIO0, Bit 1: DBCLK_GPIO0 - pmuCruGatePointer = (uint*)(_pmuCruPointer + 0x0814); - pmuCruValue = *pmuCruGatePointer; - - // CRU CLKGATE_CON9 offset is 0x0824 (GPIO1–GPIO4 pclk and dbclk) - // Bits 0–3: PCLK_GPIO1–4, Bits 4–7: DBCLK_GPIO1–4 - cruGatePointer = (uint*)(_cruPointer + 0x0824); - cruValue = *cruGatePointer; - - // software write enable - pmuCruValue |= 0b11U << 16; - cruValue |= 0xFFU << 16; - + // 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) { - // when HIGH, clock is gated (disabled); clear to enable - pmuCruValue &= ~0b11U; - cruValue &= ~0xFFU; - } - else - { - pmuCruValue |= 0b11U; - cruValue |= 0xFFU; + // 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; } - *pmuCruGatePointer = pmuCruValue; - *cruGatePointer = cruValue; + // 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() 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..fa5b8b7048 --- /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} (Ctrl+C 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} (Ctrl+C 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); + } + } + } + } +} From 721326afab856c892f9679e961c69f16ccd6de36 Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Sat, 18 Apr 2026 21:55:11 +0200 Subject: [PATCH 4/7] gpio range fix --- src/devices/Gpio/Drivers/OrangePi5BDriver.cs | 2 +- src/devices/Gpio/Drivers/OrangePi5Driver.cs | 2 +- src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs | 2 +- src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs | 2 +- src/devices/Gpio/Drivers/OrangePi5ProDriver.cs | 2 +- src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/devices/Gpio/Drivers/OrangePi5BDriver.cs b/src/devices/Gpio/Drivers/OrangePi5BDriver.cs index 9259c9256b..c1ca4193ec 100644 --- a/src/devices/Gpio/Drivers/OrangePi5BDriver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5BDriver.cs @@ -45,7 +45,7 @@ public class OrangePi5BDriver : Rk3588Driver /// The pin is not a GPIO pin (power, ground, etc.). public static int MapPhysicalPinNumber(int physicalPin) { - if (physicalPin < 0 || physicalPin >= _physicalToGpio.Length) + if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length) { throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-26).", nameof(physicalPin)); } diff --git a/src/devices/Gpio/Drivers/OrangePi5Driver.cs b/src/devices/Gpio/Drivers/OrangePi5Driver.cs index 58f720a0df..3070a72b7e 100644 --- a/src/devices/Gpio/Drivers/OrangePi5Driver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5Driver.cs @@ -45,7 +45,7 @@ public class OrangePi5Driver : Rk3588Driver /// The pin is not a GPIO pin (power, ground, etc.). public static int MapPhysicalPinNumber(int physicalPin) { - if (physicalPin < 0 || physicalPin >= _physicalToGpio.Length) + if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length) { throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-26).", nameof(physicalPin)); } diff --git a/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs b/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs index 7195c8a73e..add9a5a922 100644 --- a/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5MaxDriver.cs @@ -52,7 +52,7 @@ public class OrangePi5MaxDriver : Rk3588Driver /// The pin is not a GPIO pin (power, ground, etc.). public static int MapPhysicalPinNumber(int physicalPin) { - if (physicalPin < 0 || physicalPin >= _physicalToGpio.Length) + if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length) { throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-40).", nameof(physicalPin)); } diff --git a/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs b/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs index 90700b72dc..6a7546ea15 100644 --- a/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5PlusDriver.cs @@ -52,7 +52,7 @@ public class OrangePi5PlusDriver : Rk3588Driver /// The pin is not a GPIO pin (power, ground, etc.). public static int MapPhysicalPinNumber(int physicalPin) { - if (physicalPin < 0 || physicalPin >= _physicalToGpio.Length) + if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length) { throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-40).", nameof(physicalPin)); } diff --git a/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs b/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs index 8c65ed6435..2b23f680a9 100644 --- a/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5ProDriver.cs @@ -52,7 +52,7 @@ public class OrangePi5ProDriver : Rk3588Driver /// The pin is not a GPIO pin (power, ground, etc.). public static int MapPhysicalPinNumber(int physicalPin) { - if (physicalPin < 0 || physicalPin >= _physicalToGpio.Length) + if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length) { throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-40).", nameof(physicalPin)); } diff --git a/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs b/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs index e84540b58f..133eff4146 100644 --- a/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs @@ -52,7 +52,7 @@ public class OrangePi5UltraDriver : Rk3588Driver /// The pin is not a GPIO pin (power, ground, etc.). public static int MapPhysicalPinNumber(int physicalPin) { - if (physicalPin < 0 || physicalPin >= _physicalToGpio.Length) + if (physicalPin <= 0 || physicalPin >= _physicalToGpio.Length) { throw new ArgumentException($"Physical pin {physicalPin} is out of range (1-40).", nameof(physicalPin)); } From a3fdb308c6d0816952dd08b34bfd581c9bf88318 Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Sat, 18 Apr 2026 21:58:53 +0200 Subject: [PATCH 5/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Gpio/Drivers/Rockchip/Rk3588Driver.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs b/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs index 0635f48404..2d4fab3341 100644 --- a/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs +++ b/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs @@ -112,16 +112,34 @@ public unsafe class Rk3588Driver : RockchipDriver /// 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 in PMU1_IOC, B5–B7 in PMU2_IOC (handled as PMU2). + /// 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, PMU2_IOC + 0x0000 }, // GPIO0_B: LOW (B0–B3), HIGH (B4–B7) + { 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; From aebac3aca4f4c262b20e1fba068ce28d96d8843b Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Sat, 18 Apr 2026 22:15:03 +0200 Subject: [PATCH 6/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Gpio/Drivers/Rockchip/Rk3588Driver.cs | 98 +++++++++++++------ 1 file changed, 66 insertions(+), 32 deletions(-) diff --git a/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs b/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs index 2d4fab3341..9d98158662 100644 --- a/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs +++ b/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs @@ -450,42 +450,76 @@ private void Initialize() return; } - int fileDescriptor = Interop.open(GpioMemoryFilePath, FileOpenFlags.O_RDWR | FileOpenFlags.O_SYNC); - if (fileDescriptor == -1) + int fileDescriptor = -1; + int mapLength = Environment.SystemPageSize * 16; + IntPtr iocMap = IntPtr.Zero; + IntPtr cruMap = IntPtr.Zero; + IntPtr pmuCruMap = IntPtr.Zero; + IntPtr mapFailed = new IntPtr(-1); + bool success = false; + + try { - throw new IOException($"Error {Marshal.GetLastWin32Error()} initializing the Gpio driver."); + 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; } - - // IOC register region: covers all IOC domains (PMU1 through EMMC, ~56 KB) - IntPtr iocMap = Interop.mmap(IntPtr.Zero, Environment.SystemPageSize * 16, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, MemoryMappedFlags.MAP_SHARED, fileDescriptor, (int)(IoControllerBase & ~_mapMask)); - // CRU register region: clock gating for GPIO1–GPIO4 (CLKGATE_CON9 at offset 0x0824) - IntPtr cruMap = Interop.mmap(IntPtr.Zero, Environment.SystemPageSize * 16, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, MemoryMappedFlags.MAP_SHARED, fileDescriptor, (int)(ClockResetUnit & ~_mapMask)); - // PMU CRU register region: clock gating for GPIO0 (PMU_CLKGATE_CON5 at offset 0x0814) - IntPtr pmuCruMap = Interop.mmap(IntPtr.Zero, Environment.SystemPageSize * 16, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, MemoryMappedFlags.MAP_SHARED, fileDescriptor, (int)(PmuClockResetUnit & ~_mapMask)); - - if (iocMap.ToInt64() == -1) - { - Interop.munmap(iocMap, 0); - throw new IOException($"Error {Marshal.GetLastWin32Error()} initializing the Gpio driver (IOC initialize error)."); - } - - if (cruMap.ToInt64() == -1) + finally { - Interop.munmap(cruMap, 0); - throw new IOException($"Error {Marshal.GetLastWin32Error()} initializing the Gpio driver (CRU initialize error)."); + 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); + } + } } - - if (pmuCruMap.ToInt64() == -1) - { - Interop.munmap(pmuCruMap, 0); - throw new IOException($"Error {Marshal.GetLastWin32Error()} initializing the Gpio driver (PMU CRU initialize error)."); - } - - _iocPointer = iocMap; - _cruPointer = cruMap; - _pmuCruPointer = pmuCruMap; - - Interop.close(fileDescriptor); } } } From 846daf9a56eb234156a52f8d6db081ed9adc2112 Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Sat, 18 Apr 2026 22:24:25 +0200 Subject: [PATCH 7/7] last PR feedback fixes --- src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs | 2 +- src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs | 14 +++++++------- src/devices/Gpio/samples/orangepi5/Program.cs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs b/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs index 133eff4146..b0e7d203d0 100644 --- a/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs +++ b/src/devices/Gpio/Drivers/OrangePi5UltraDriver.cs @@ -9,7 +9,7 @@ namespace Iot.Device.Gpio.Drivers /// A GPIO driver for the Orange Pi 5 Ultra (40-pin header). /// /// - /// SoC: Rockchip RK3588 + /// SoC: Rockchip RK3588S /// Pin mapping sourced from wiringOP (orangepi-xunlong/wiringOP). /// public class OrangePi5UltraDriver : Rk3588Driver diff --git a/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs b/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs index 9d98158662..1082f35b10 100644 --- a/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs +++ b/src/devices/Gpio/Drivers/Rockchip/Rk3588Driver.cs @@ -45,6 +45,8 @@ public unsafe class Rk3588Driver : RockchipDriver 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. @@ -285,9 +287,7 @@ protected override void SetPinMode(int pinNumber, PinMode mode) if (unmapped.GpioNumber == 0) { - int pmuIomuxOffset = unmapped.PortNumber < 4 - ? _gpio0PmuIomux[unmapped.Port, 0] - : _gpio0PmuIomux[unmapped.Port, 1]; + int pmuIomuxOffset = GetGpio0PmuIomuxOffset(unmapped.Port, unmapped.PortNumber); pmuIomuxPointer = (uint*)(_iocPointer + pmuIomuxOffset); // Write-enable for 4 mux bits, data = 0 (GPIO mode) @@ -350,19 +350,19 @@ protected override void Dispose(bool disposing) if (_iocPointer != IntPtr.Zero) { - Interop.munmap(_iocPointer, 0); + Interop.munmap(_iocPointer, s_mappingLength); _iocPointer = IntPtr.Zero; } if (_cruPointer != IntPtr.Zero) { - Interop.munmap(_cruPointer, 0); + Interop.munmap(_cruPointer, s_mappingLength); _cruPointer = IntPtr.Zero; } if (_pmuCruPointer != IntPtr.Zero) { - Interop.munmap(_pmuCruPointer, 0); + Interop.munmap(_pmuCruPointer, s_mappingLength); _pmuCruPointer = IntPtr.Zero; } @@ -451,7 +451,7 @@ private void Initialize() } int fileDescriptor = -1; - int mapLength = Environment.SystemPageSize * 16; + int mapLength = s_mappingLength; IntPtr iocMap = IntPtr.Zero; IntPtr cruMap = IntPtr.Zero; IntPtr pmuCruMap = IntPtr.Zero; diff --git a/src/devices/Gpio/samples/orangepi5/Program.cs b/src/devices/Gpio/samples/orangepi5/Program.cs index fa5b8b7048..1bfb584e72 100644 --- a/src/devices/Gpio/samples/orangepi5/Program.cs +++ b/src/devices/Gpio/samples/orangepi5/Program.cs @@ -30,7 +30,7 @@ private static void Main(string[] args) // 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} (Ctrl+C to exit)..."); + Console.WriteLine($"Output mode: toggling pin {pin} (press any key to exit)..."); bool high = false; while (!Console.KeyAvailable) @@ -47,7 +47,7 @@ private static void Main(string[] args) // 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} (Ctrl+C to exit)..."); + Console.WriteLine($"Input mode: reading pin {pin} (press any key to exit)..."); PinValue lastState = controller.Read(pin); Console.WriteLine($"Initial state: {lastState}");