RaspberryPi3Driver.Linux.cs 15.6 KB
Newer Older
1 2 3 4
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

5 6
using System.Collections.Generic;
using System.IO;
7
using System.Threading;
8
using System.Threading.Tasks;
9

10
namespace System.Device.Gpio.Drivers
11
{
12
    public unsafe partial class RaspberryPi3Driver : GpioDriver
13
    {
14 15
        private RegisterView* _registerViewPointer = null;
        private const int GpioRegisterOffset = 0x00;
G
Greg Ingram 已提交
16 17
        private static readonly object s_initializationLock = new object();
        private static readonly object s_sysFsInitializationLock = new object();
18 19
        private const string GpioMemoryFilePath = "/dev/gpiomem";
        private UnixDriver _sysFSDriver = null;
G
Greg Ingram 已提交
20
        private readonly IDictionary<int, PinMode> _sysFSModes = new Dictionary<int, PinMode>();
21

22
        protected override void Dispose(bool disposing)
23
        {
24 25 26 27 28 29 30 31 32 33 34 35
            if (_registerViewPointer != null)
            {
                Interop.munmap((IntPtr)_registerViewPointer, 0);
                _registerViewPointer = null;
            }
            if (_sysFSDriver != null)
            {
                _sysFSDriver.Dispose();
                _sysFSDriver = null;
            }
        }

G
Greg Ingram 已提交
36 37 38 39 40
        /// <summary>
        /// Gets the mode of a pin for Unix.
        /// </summary>
        /// <param name="mode">The mode of a pin to get.</param>
        /// <returns>The mode of a pin for Unix.</returns>
41 42 43 44 45 46 47 48 49 50 51 52 53
        private PinMode GetModeForUnixDriver(PinMode mode)
        {
            switch (mode)
            {
                case PinMode.Input:
                case PinMode.InputPullUp:
                case PinMode.InputPullDown:
                    return PinMode.Input;
                case PinMode.Output:
                    return PinMode.Output;
                default:
                    throw new InvalidOperationException($"Can not parse pin mode {_sysFSModes}");
            }
54 55
        }

G
Greg Ingram 已提交
56 57 58 59 60 61
        /// <summary>
        /// Adds a handler for a pin value changed event.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
        /// <param name="eventTypes">The event types to wait for.</param>
        /// <param name="callback">Delegate that defines the structure for callbacks when a pin value changed event occurs.</param>
62
        protected internal override void AddCallbackForPinValueChangedEvent(int pinNumber, PinEventTypes eventType, PinChangeEventHandler callback)
63
        {
64 65 66 67 68 69 70
            ValidatePinNumber(pinNumber);
            InitializeSysFS();

            _sysFSDriver.OpenPin(pinNumber);
            _sysFSDriver.SetPinMode(pinNumber, GetModeForUnixDriver(_sysFSModes[pinNumber]));

            _sysFSDriver.AddCallbackForPinValueChangedEvent(pinNumber, eventType, callback);
71 72
        }

G
Greg Ingram 已提交
73 74 75 76
        /// <summary>
        /// Closes an open pin.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
77
        protected internal override void ClosePin(int pinNumber)
78
        {
79 80 81 82 83 84 85
            ValidatePinNumber(pinNumber);

            if (_sysFSModes.ContainsKey(pinNumber) && _sysFSModes[pinNumber] == PinMode.Output)
            {
                Write(pinNumber, PinValue.Low);
                SetPinMode(pinNumber, PinMode.Input);
            }
86 87
        }

G
Greg Ingram 已提交
88 89 90 91 92 93
        /// <summary>
        /// Checks if a pin supports a specific mode.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
        /// <param name="mode">The mode to check.</param>
        /// <returns>The status if the pin supports the mode.</returns>
94
        protected internal override bool IsPinModeSupported(int pinNumber, PinMode mode)
95
        {
G
Greg Ingram 已提交
96
            switch (mode)
97 98 99 100 101 102 103 104 105
            {
                case PinMode.Input:
                case PinMode.InputPullDown:
                case PinMode.InputPullUp:
                case PinMode.Output:
                    return true;
                default:
                    return false;
            }
106 107
        }

G
Greg Ingram 已提交
108 109 110 111
        /// <summary>
        /// Opens a pin in order for it to be ready to use.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
112
        protected internal override void OpenPin(int pinNumber)
113
        {
114 115 116
            ValidatePinNumber(pinNumber);
            Initialize();
            SetPinMode(pinNumber, PinMode.Input);
117 118
        }

G
Greg Ingram 已提交
119 120 121 122 123
        /// <summary>
        /// Reads the current value of a pin.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
        /// <returns>The value of the pin.</returns>
124
        protected internal unsafe override PinValue Read(int pinNumber)
125
        {
126 127 128 129 130 131 132 133 134
            ValidatePinNumber(pinNumber);

            /*
             * There are two registers that contain the value of a pin. Each hold the value of 32 
             * different pins. 1 bit represents the value of a pin, 0 is PinValue.Low and 1 is PinValue.High
             */

            uint register = _registerViewPointer->GPLEV[pinNumber / 32];
            return Convert.ToBoolean((register >> (pinNumber % 32)) & 1) ? PinValue.High : PinValue.Low;
135 136
        }

G
Greg Ingram 已提交
137 138 139 140 141
        /// <summary>
        /// Removes a handler for a pin value changed event.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
        /// <param name="callback">Delegate that defines the structure for callbacks when a pin value changed event occurs.</param>
142
        protected internal override void RemoveCallbackForPinValueChangedEvent(int pinNumber, PinChangeEventHandler callback)
143
        {
144 145 146 147 148 149 150
            ValidatePinNumber(pinNumber);
            InitializeSysFS();

            _sysFSDriver.OpenPin(pinNumber);
            _sysFSDriver.SetPinMode(pinNumber, GetModeForUnixDriver(_sysFSModes[pinNumber]));

            _sysFSDriver.RemoveCallbackForPinValueChangedEvent(pinNumber, callback);
151 152
        }

G
Greg Ingram 已提交
153 154 155 156 157
        /// <summary>
        /// Sets the mode to a pin.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
        /// <param name="mode">The mode to be set.</param>
158 159
        protected internal override void SetPinMode(int pinNumber, PinMode mode)
        {
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
            ValidatePinNumber(pinNumber);
            IsPinModeSupported(pinNumber, mode);

            /*
             * There are 6 registers(4-byte ints) that control the mode for all pins. Each
             * register controls the mode for 10 pins. Each pin uses 3 bits in the register
             * containing the mode.
             */

            // Define the shift to get the right 3 bits in the register
            int shift = (pinNumber % 10) * 3;
            // Gets a pointer to the register that holds the mode for the pin
            uint* registerPointer = &_registerViewPointer->GPFSEL[pinNumber / 10];
            uint register = *registerPointer;
            // Clear the 3 bits to 0 for the pin Number.
            register &= ~(0b111U << shift);
            // Set the 3 bits to the desired mode for that pin.
            register |= (mode == PinMode.Output ? 1u : 0u) << shift;
            *registerPointer = register;
G
Greg Ingram 已提交
179

180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
            if (_sysFSModes.ContainsKey(pinNumber))
            {
                _sysFSModes[pinNumber] = mode;
            }
            else
            {
                _sysFSModes.Add(pinNumber, mode);
            }

            if (mode != PinMode.Output)
            {
                SetInputPullMode(pinNumber, mode);
            }
        }

G
Greg Ingram 已提交
195 196 197 198 199
        /// <summary>
        /// Sets the resistor pull up/down mode for an input pin.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
        /// <param name="mode">The mode of a pin to set the resistor pull up/down mode.</param>
200 201 202
        private void SetInputPullMode(int pinNumber, PinMode mode)
        {
            byte modeToPullMode;
G
Greg Ingram 已提交
203
            switch (mode)
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
            {
                case PinMode.Input:
                    modeToPullMode = 0;
                    break;
                case PinMode.InputPullDown:
                    modeToPullMode = 1;
                    break;
                case PinMode.InputPullUp:
                    modeToPullMode = 2;
                    break;
                default:
                    throw new ArgumentException($"{mode} is not supported as a pull up/down mode.");
            }

            /*
             * This is the process outlined by the BCM2835 datasheet on how to set the pull mode.
             * The GPIO Pull - up/down Clock Registers control the actuation of internal pull-downs on the respective GPIO pins.
             * These registers must be used in conjunction with the GPPUD register to effect GPIO Pull-up/down changes.
             * The following sequence of events is required: 
             * 
             * 1. Write to GPPUD to set the required control signal (i.e.Pull-up or Pull-Down or neither to remove the current Pull-up/down)
             * 2. Wait 150 cycles – this provides the required set-up time for the control signal 
             * 3. Write to GPPUDCLK0/1 to clock the control signal into the GPIO pads you wish to modify
             *    – NOTE only the pads which receive a clock will be modified, all others will retain their previous state.
             * 4. Wait 150 cycles – this provides the required hold time for the control signal 
             * 5. Write to GPPUD to remove the control signal 
             * 6. Write to GPPUDCLK0/1 to remove the clock
             */

            uint* gppudPointer = &_registerViewPointer->GPPUD;
            uint register = *gppudPointer;
            register &= ~0b11U;
            register |= modeToPullMode;
            *gppudPointer = register;

            // Wait 150 cycles – this provides the required set-up time for the control signal
            Thread.SpinWait(150);

            int index = pinNumber / 32;
            int shift = pinNumber % 32;
            uint* gppudclkPointer = &_registerViewPointer->GPPUDCLK[index];
            register = *gppudclkPointer;
            register |= 1U << shift;
            *gppudclkPointer = register;

            // Wait 150 cycles – this provides the required hold time for the control signal 
            Thread.SpinWait(150);

            register = *gppudPointer;
            register &= ~0b11U;
            *gppudPointer = register;
            *gppudclkPointer = 0;
256 257
        }

G
Greg Ingram 已提交
258 259 260 261 262 263 264 265
        /// <summary>
        /// Blocks execution until an event of type eventType is received or a cancellation is requested.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
        /// <param name="eventTypes">The event types to wait for.</param>
        /// <param name="cancellationToken">The cancellation token of when the operation should stop waiting for an event.</param>
        /// <returns>A structure that contains the result of the waiting operation.</returns>
        protected internal override WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, CancellationToken cancellationToken)
266
        {
267 268 269 270 271 272
            ValidatePinNumber(pinNumber);
            InitializeSysFS();

            _sysFSDriver.OpenPin(pinNumber);
            _sysFSDriver.SetPinMode(pinNumber, GetModeForUnixDriver(_sysFSModes[pinNumber]));

G
Greg Ingram 已提交
273
            return _sysFSDriver.WaitForEvent(pinNumber, eventTypes, cancellationToken);
274 275
        }

G
Greg Ingram 已提交
276 277 278 279 280 281 282 283
        /// <summary>
        /// Async call until an event of type eventType is received or a cancellation is requested.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
        /// <param name="eventTypes">The event types to wait for.</param>
        /// <param name="token">The cancellation token of when the operation should stop waiting for an event.</param>
        /// <returns>A task representing the operation of getting the structure that contains the result of the waiting operation</returns>
        protected internal override ValueTask<WaitForEventResult> WaitForEventAsync(int pinNumber, PinEventTypes eventTypes, CancellationToken cancellationToken)
284
        {
285 286 287 288 289 290
            ValidatePinNumber(pinNumber);
            InitializeSysFS();

            _sysFSDriver.OpenPin(pinNumber);
            _sysFSDriver.SetPinMode(pinNumber, GetModeForUnixDriver(_sysFSModes[pinNumber]));

G
Greg Ingram 已提交
291
            return _sysFSDriver.WaitForEventAsync(pinNumber, eventTypes, cancellationToken);
292 293
        }

G
Greg Ingram 已提交
294 295 296 297 298
        /// <summary>
        /// Writes a value to a pin.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
        /// <param name="value">The value to be written to the pin.</param>
299
        protected internal override void Write(int pinNumber, PinValue value)
300
        {
301 302 303 304 305 306 307 308 309 310 311 312 313
            ValidatePinNumber(pinNumber);

            /*
             * If the value is High, GPSET register is used. Otherwise, GPCLR will be used. For
             * both cases, a 1 is set on the corresponding bit in the register in order to set 
             * the desired value.
             */

            uint* registerPointer = (value == PinValue.High) ? &_registerViewPointer->GPSET[pinNumber / 32] : &_registerViewPointer->GPCLR[pinNumber / 32];
            uint register = *registerPointer;
            register = 1U << (pinNumber % 32);
            *registerPointer = register;
        }
G
Greg Ingram 已提交
314

315 316 317 318 319 320
        private void InitializeSysFS()
        {
            if (_sysFSDriver != null)
            {
                return;
            }
G
Greg Ingram 已提交
321
            lock (s_sysFsInitializationLock)
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
            {
                if (_sysFSDriver != null)
                {
                    return;
                }
                _sysFSDriver = new UnixDriver();
            }
        }

        private void Initialize()
        {
            if (_registerViewPointer != null)
            {
                return;
            }

G
Greg Ingram 已提交
338
            lock (s_initializationLock)
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
            {
                if (_registerViewPointer != null)
                {
                    return;
                }

                int fileDescriptor = Interop.open(GpioMemoryFilePath, FileOpenFlags.O_RDWR | FileOpenFlags.O_SYNC);
                if (fileDescriptor < 0)
                {
                    throw new IOException("Error initializing the Gpio driver.");
                }

                IntPtr mapPointer = Interop.mmap(IntPtr.Zero, Environment.SystemPageSize, (MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE), MemoryMappedFlags.MAP_SHARED, fileDescriptor, GpioRegisterOffset);
                if (mapPointer.ToInt32() < 0)
                {
                    throw new IOException("Error initializing the Gpio driver.");
                }

                Interop.close(fileDescriptor);
                _registerViewPointer = (RegisterView*)mapPointer;
            }
        }

G
Greg Ingram 已提交
362 363 364 365 366
        /// <summary>
        /// Gets the mode of a pin.
        /// </summary>
        /// <param name="pinNumber">The pin number in the driver's logical numbering scheme.</param>
        /// <returns>The mode of the pin.</returns>
367 368 369 370 371 372
        protected internal override PinMode GetPinMode(int pinNumber)
        {
            ValidatePinNumber(pinNumber);

            if (!_sysFSModes.ContainsKey(pinNumber))
            {
G
Greg Ingram 已提交
373
                throw new InvalidOperationException("Can not get a pin mode of a pin that is not open.");
374 375
            }
            return _sysFSModes[pinNumber];
376 377 378
        }
    }
}